diff --git a/src/DynamicObj/DynObj.fs b/src/DynamicObj/DynObj.fs
index b79ad42..8683d86 100644
--- a/src/DynamicObj/DynObj.fs
+++ b/src/DynamicObj/DynObj.fs
@@ -216,4 +216,34 @@ module DynObj =
/// Prints a formatted string containing all static and dynamic properties of the given DynamicObj
///
/// The DynamicObj for which to print a formatted string for
- let print (dynObj:DynamicObj) = printfn "%s" (dynObj |> format)
\ No newline at end of file
+ let print (dynObj:DynamicObj) = printfn "%s" (dynObj |> format)
+
+ ///
+ /// function to deep copy a boxed object (if possible)
+ ///
+ /// The following cases are handled (in this precedence):
+ ///
+ /// - Basic F# types (bool, byte, sbyte, int16, uint16, int, uint, int64, uint64, nativeint, unativeint, float, float32, char, string, unit, decimal)
+ ///
+ /// - ResizeArrays and Dictionaries containing any combination of basic F# types
+ ///
+ /// - Dictionaries containing DynamicObj as keys or values in any combination with DynamicObj or basic F# types as keys or values
+ ///
+ /// - array<DynamicObj>, list<DynamicObj>, ResizeArray<DynamicObj>: These collections of DynamicObj are copied as a new collection with recursively deep copied elements.
+ ///
+ /// - System.ICloneable: If the property implements ICloneable, the Clone() method is called on the property.
+ ///
+ /// - DynamicObj (and derived classes): properties that are themselves DynamicObj instances are deep copied recursively.
+ /// if a derived class has static properties (e.g. instance properties), these will be copied as dynamic properties on the new instance.
+ ///
+ /// Note on Classes that inherit from DynamicObj:
+ ///
+ /// Classes that inherit from DynamicObj will match the `DynamicObj` typecheck if they do not implement ICloneable.
+ /// The deep copied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
+ /// It should be possible to 'recover' the original type by checking if the needed properties exist as dynamic properties,
+ /// and then passing them to the class constructor if needed.
+ ///
+ /// The object that should be deep copied
+ /// Whether to include instance properties (= 'static' properties on the class) as dynamic properties on the new instance for matched DynamicObj.
+ let tryDeepCopyObj (includeInstanceProperties:bool) (o:DynamicObj) =
+ CopyUtils.tryDeepCopyObj(o, includeInstanceProperties)
\ No newline at end of file
diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs
index a0c7276..ae6be8c 100644
--- a/src/DynamicObj/DynamicObj.fs
+++ b/src/DynamicObj/DynamicObj.fs
@@ -267,7 +267,11 @@ type DynamicObj() =
///
/// The following cases are handled (in this precedence):
///
- /// - Basic F# types (int, float, bool, string, char, byte, sbyte, int16, uint16, int32, uint32, int64, uint64, single, decimal)
+ /// - Basic F# types (bool, byte, sbyte, int16, uint16, int, uint, int64, uint64, nativeint, unativeint, float, float32, char, string, unit, decimal)
+ ///
+ /// - ResizeArrays and Dictionaries containing any combination of basic F# types
+ ///
+ /// - Dictionaries containing DynamicObj as keys or values in any combination with DynamicObj or basic F# types as keys or values
///
/// - array<DynamicObj>, list<DynamicObj>, ResizeArray<DynamicObj>: These collections of DynamicObj are copied as a new collection with recursively deep copied elements.
///
@@ -279,16 +283,21 @@ type DynamicObj() =
/// Note on Classes that inherit from DynamicObj:
///
/// Classes that inherit from DynamicObj will match the `DynamicObj` typecheck if they do not implement ICloneable.
- /// The deep coopied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
+ /// The deep copied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
/// It should be possible to 'recover' the original type by checking if the needed properties exist as dynamic properties,
/// and then passing them to the class constructor if needed.
///
/// The target object to copy dynamic members to
- /// Whether existing properties on the target object will be overwritten
- member this.DeepCopyPropertiesTo(target:#DynamicObj, ?overWrite) =
+ /// Whether existing properties on the target object will be overwritten. Default is false
+ /// Whether to include instance properties (= 'static' properties on the class) as dynamic properties on the new instance. Default is true
+ member this.DeepCopyPropertiesTo(
+ target:#DynamicObj,
+ ?overWrite: bool,
+ ?includeInstanceProperties:bool
+ ) =
let overWrite = defaultArg overWrite false
-
- this.GetProperties(true)
+ let includeInstanceProperties = defaultArg includeInstanceProperties true
+ this.GetProperties(includeInstanceProperties)
|> Seq.iter (fun kv ->
match target.TryGetPropertyHelper kv.Key with
| Some pi when overWrite -> pi.SetValue target (CopyUtils.tryDeepCopyObj kv.Value)
@@ -307,7 +316,11 @@ type DynamicObj() =
///
/// The following cases are handled (in this precedence):
///
- /// - Basic F# types (int, float, bool, string, char, byte, sbyte, int16, uint16, int32, uint32, int64, uint64, single, decimal)
+ /// - Basic F# types (bool, byte, sbyte, int16, uint16, int, uint, int64, uint64, nativeint, unativeint, float, float32, char, string, unit, decimal)
+ ///
+ /// - ResizeArrays and Dictionaries containing any combination of basic F# types
+ ///
+ /// - Dictionaries containing DynamicObj as keys or values in any combination with DynamicObj or basic F# types as keys or values
///
/// - array<DynamicObj>, list<DynamicObj>, ResizeArray<DynamicObj>: These collections of DynamicObj are copied as a new collection with recursively deep copied elements.
///
@@ -319,19 +332,20 @@ type DynamicObj() =
/// Note on Classes that inherit from DynamicObj:
///
/// Classes that inherit from DynamicObj will match the `DynamicObj` typecheck if they do not implement ICloneable.
- /// The deep coopied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
+ /// The deep copied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
/// It should be possible to 'recover' the original type by checking if the needed properties exist as dynamic properties,
/// and then passing them to the class constructor if needed.
///
- /// The target object to copy dynamic members to
- /// Whether existing properties on the target object will be overwritten
- member this.DeepCopyProperties() = CopyUtils.tryDeepCopyObj this
+ /// Whether to include instance properties (= 'static' properties on the class) as dynamic properties on the new instance. Default is true
+ member this.DeepCopyProperties(?includeInstanceProperties:bool) =
+ let includeInstanceProperties = defaultArg includeInstanceProperties true
+ CopyUtils.tryDeepCopyObj(this, includeInstanceProperties)
#if !FABLE_COMPILER
// Some necessary overrides for methods inherited from System.Dynamic.DynamicObject()
//
// Needed mainly for making Newtonsoft.Json Serialization work
- override this.TryGetMember(binder:GetMemberBinder,result:obj byref ) =
+ override this.TryGetMember(binder:GetMemberBinder,result:obj byref) =
match this.TryGetPropertyValue binder.Name with
| Some value -> result <- value; true
| None -> false
@@ -388,18 +402,63 @@ type DynamicObj() =
and CopyUtils =
- /// internal helper function to deep copy a boxed object (if possible)
- static member tryDeepCopyObj (o:obj) =
+ ///
+ /// function to deep copy a boxed object (if possible)
+
+ /// The following cases are handled (in this precedence):
+ ///
+ /// - Basic F# types (bool, byte, sbyte, int16, uint16, int, uint, int64, uint64, nativeint, unativeint, float, float32, char, string, unit, decimal)
+ ///
+ /// - ResizeArrays and Dictionaries containing any combination of basic F# types
+ ///
+ /// - Dictionaries containing DynamicObj as keys or values in any combination with DynamicObj or basic F# types as keys or values
+ ///
+ /// - array<DynamicObj>, list<DynamicObj>, ResizeArray<DynamicObj>: These collections of DynamicObj are copied as a new collection with recursively deep copied elements.
+ ///
+ /// - System.ICloneable: If the property implements ICloneable, the Clone() method is called on the property.
+ ///
+ /// - DynamicObj (and derived classes): properties that are themselves DynamicObj instances are deep copied recursively.
+ /// if a derived class has static properties (e.g. instance properties), these will be copied as dynamic properties on the new instance.
+ ///
+ /// Note on Classes that inherit from DynamicObj:
+ ///
+ /// Classes that inherit from DynamicObj will match the `DynamicObj` typecheck if they do not implement ICloneable.
+ /// The deep copied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
+ /// It should be possible to 'recover' the original type by checking if the needed properties exist as dynamic properties,
+ /// and then passing them to the class constructor if needed.
+ ///
+ /// The object that should be deep copied
+ /// Whether to include instance properties (= 'static' properties on the class) as dynamic properties on the new instance for matched DynamicObj. Default is true
+ static member tryDeepCopyObj(
+ o:obj,
+ ?includeInstanceProperties:bool
+ ) =
+ let includeInstanceProperties = defaultArg includeInstanceProperties true
+
let rec tryDeepCopyObj (o:obj) =
match o with
// might be that we do not need this case, however if we remove it, some types will match the
// ICloneable case in transpiled code, which we'd like to prevent, so well keep it for now.
- | :? int | :? float | :? bool
- | :? string | :? char | :? byte
- | :? sbyte | :? int16 | :? uint16
- | :? int32 | :? uint32 | :? int64
- | :? uint64 | :? single
+ // https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/basic-types
+ | :? bool
+ | :? byte
+ | :? sbyte
+ | :? int16
+ | :? uint16
+ | :? int
+ | :? uint
+ | :? int64
+ | :? uint64
+ #if !FABLE_COMPILER
+ | :? nativeint
+ | :? unativeint
+ #endif
+ | :? float
+ | :? float32
+ | :? char
+ | :? string
+ | :? unit
-> o
#if !FABLE_COMPILER_PYTHON
@@ -407,12 +466,513 @@ and CopyUtils =
| :? decimal -> o
#endif
+ #if !FABLE_COMPILER
+
+ // we can do some more type checking in F# land
+
+ // ResizeArray typematches are all translated as `isArrayLike` in Fable JS/Py, so all these cases are the same in transpiled code.
+ // However this is fine, as we can transpile one single case (the `ResizeArray` case) that recursively applies `tryDeepCopyObj` on all items.
+ // That way, everything is fine in the untyped world, and we can keep the boxed types in F#.
+
+ // ResizeArrays are mutable and we need to copy them. For primitives, we can do this easily.
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+ | :? ResizeArray as r -> ResizeArray(r) |> box
+
+ // Dictionaries are mutable and we need to copy them. For primitives, we can do this easily, it is just a lot of work to write man
+ // Fable does simply not compile these typechecks, i guess because everything is an object/dictionary in JS/PY.
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary as dict -> Dictionary(dict) |> box
+ | :? Dictionary