DynamicObj
The primary use case of DynamicObj is the extension of F# classes with dynamic properties. This is useful when you want to add arbitrarily typed properties to a class at runtime.
Why would you want to do that?
Yes, The type system is one of the core strengths of F#, and it is awesome. However, there are cases where a static domain model is either unfeasible or not flexible enough, especially when interfacing with dynamic languages such as JavaScript or Python.
DynamicObj is transpilable into JS and Python via Fable, meaning you can use it to create classes that are usable in both .NET and those languages, while making their usage (e.g., the setting of dynamic properties) both safe in .NET and idiomatic in JS/Python.
Get started
#r "nuget: Fable.Core" // Needed if working with Fable
open DynamicObj
open Fable.Core // Needed if working with Fable
[<AttachMembers>] // AttachMembers needed if working with Fable
type Person(id : int, name : string) =
// Include this in your class
inherit DynamicObj()
// Mutable static property
let mutable _name = name
member this.Name
with get() = _name
and set(value) = _name <- value
// Immutable static property
member this.ID
with get() = id
let p = Person(1337,"John")
Accessing static and dynamic properties
Any class inheriting from DynamicObj
can have static and dynamic properties, and both are accessible via various instance methods.
Access Static Properties:
p.Name
|
p.GetPropertyValue("Name")
|
Overwrite mutable static property
p.SetProperty("Name","Jane")
p.GetPropertyValue("Name")
|
You cannot overwrite mutable static properties
p.SetProperty("ID",1234) // throws an excpection
p.GetPropertyValue("ID")
|
Set dynamic properties
p.SetProperty("Address","FunStreet")
p.GetPropertyValue("Address")
|
Safe and typed access to dynamic properties
Note that all properties returted by GetPropertyValue
are boxed in .NET.
If you want to get the value in a typed manner, you can use the TryGetTypedPropertyValue
method:
p.TryGetTypedPropertyValue<string>("Name")
|
p.TryGetTypedPropertyValue<int>("Name")
|
p.TryGetTypedPropertyValue<string>("I Do Not Exist")
|
Attention: the TryGetTypedPropertyValue<'T> method is not transpilable via Fable as it can only provide access to the types known at transpilation. However, You can use the respective module function to transpile typed dynamic member access.
The DynObj module
This module provides a lot of API functions that are not not desired as static methods on DynamicObj
, as it would be confusing if they ended up on inheriting classes.
It also supports pipeline chaining.
p
|> DynObj.tryGetTypedPropertyValue<int> "ID"
|
p
|> DynObj.withProperty "Another" "prop"
|> DynObj.withProperty "Yes" 42
|> DynObj.withoutProperty "Address"
|> DynObj.withOptionalProperty "Maybe" (Some "yes")
|> DynObj.withOptionalProperty "Maybe not" None
|> DynObj.format
|
Serialization
Serialization to a JSON string that contains both static and dynamic properties is supported out-of-the-box when using Newtonsoft.Json:
#r "nuget: Newtonsoft.Json"
open Newtonsoft.Json
p
|> JsonConvert.SerializeObject
|
namespace DynamicObj
--------------------
type DynamicObj = inherit DynamicObject new: unit -> DynamicObj member DeepCopyProperties: unit -> obj member DeepCopyPropertiesTo: target: #DynamicObj * ?overWrite: bool -> unit override Equals: o: obj -> bool override GetDynamicMemberNames: unit -> IEnumerable<string> override GetHashCode: unit -> int member GetProperties: includeInstanceProperties: bool -> KeyValuePair<string,obj> seq member GetPropertyHelpers: includeInstanceProperties: bool -> PropertyHelper seq member GetPropertyNames: includeInstanceProperties: bool -> string seq ...
--------------------
new: unit -> DynamicObj
type AttachMembersAttribute = inherit Attribute new: unit -> AttachMembersAttribute
<summary> Used on a class to attach all members, useful when you want to use the class from JS. </summary>
--------------------
new: unit -> AttachMembersAttribute
type Person = inherit DynamicObj new: id: int * name: string -> Person member ID: int member Name: string with get, set
--------------------
new: id: int * name: string -> Person
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
val string: value: 'T -> string
--------------------
type string = System.String
<summary> This module contains lots of API functions for DynamicObj. These functions are not static methods on the DynamicObj type itself because that type is designed to be inherited from, and a lot of these functions might not make sense as static methods on inheriting types. </summary>
<summary> Returns Some('TPropertyValue) when a dynamic (or static) property with the given name and type exists on the input, otherwise None. </summary>
<param name="propertyName">the name of the property to get</param>
<param name="dynObj">the input DynamicObj</param>
<summary> Sets the dynamic (or static) property value with the given name, creating a new dynamic property if none exists on the given DynamicObj and returns it. </summary>
<param name="propertyName">The name of the property to set</param>
<param name="propertyValue">The value of the property to set</param>
<param name="dynObj">The DynamicObj to set the property on</param>
<remarks>This function mutates the input DynamicObj</remarks>
<summary> Removes any dynamic property with the given name from the input DynamicObj and returns it. If the property is static and mutable, it will be set to null. Static immutable properties cannot be removed. </summary>
<param name="propertyName">The name of the property to remove</param>
<param name="dynObj">The DynamicObj to remove the property from</param>
<remarks>This function mutates the input DynamicObj</remarks>
<exception cref="System.MemberAccessException">Thrown if the dynamic property does not exist</exception>
<summary> Sets the dynamic (or static) property value with the given name on the given DynamicObj if the value is Some('TPropertyValue), creating a new dynamic property if none exists, and returns it. If the given propertyValue is None, does nothing to the input DynamicObj. </summary>
<param name="propertyName">The name of the property to set</param>
<param name="propertyValue">The value of the property to set</param>
<param name="dynObj">The DynamicObj to set the property on</param>
<remarks>This function mutates the input DynamicObj</remarks>
<summary> Returns a formatted string containing all static and dynamic properties of the given DynamicObj </summary>
<param name="dynObj">The DynamicObj for which to generate a formatted string for</param>
<summary> Provides methods for converting between .NET types and JSON types. </summary>
<example><code lang="cs" source="..\Src\Newtonsoft.Json.Tests\Documentation\SerializationTests.cs" region="SerializeObject" title="Serializing and Deserializing JSON with JsonConvert" /></example>
JsonConvert.SerializeObject(value: obj, settings: JsonSerializerSettings) : string
JsonConvert.SerializeObject(value: obj, [<System.ParamArray>] converters: JsonConverter array) : string
JsonConvert.SerializeObject(value: obj, formatting: Formatting) : string
JsonConvert.SerializeObject(value: obj, formatting: Formatting, settings: JsonSerializerSettings) : string
JsonConvert.SerializeObject(value: obj, ``type`` : System.Type, settings: JsonSerializerSettings) : string
JsonConvert.SerializeObject(value: obj, formatting: Formatting, [<System.ParamArray>] converters: JsonConverter array) : string
JsonConvert.SerializeObject(value: obj, ``type`` : System.Type, formatting: Formatting, settings: JsonSerializerSettings) : string