Header menu logo DynamicObj

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
"John"
p.GetPropertyValue("Name")
"John"

Overwrite mutable static property

p.SetProperty("Name","Jane")
p.GetPropertyValue("Name")
"Jane"

You cannot overwrite mutable static properties

p.SetProperty("ID",1234) // throws an excpection
p.GetPropertyValue("ID")
1337

Set dynamic properties

p.SetProperty("Address","FunStreet")
p.GetPropertyValue("Address")
"FunStreet"

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")
Some "Jane"
p.TryGetTypedPropertyValue<int>("Name")
<null>
p.TryGetTypedPropertyValue<string>("I Do Not Exist")
<null>

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"
Some 1337
p
|> DynObj.withProperty "Another" "prop"
|> DynObj.withProperty "Yes" 42
|> DynObj.withoutProperty "Address"
|> DynObj.withOptionalProperty "Maybe" (Some "yes")
|> DynObj.withOptionalProperty "Maybe not" None
|> DynObj.format
"Name: Jane
ID: 1337
?Maybe: yes
?Another: prop
?Yes: 42"

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
"{"Name":"Jane","ID":1337,"Maybe":"yes","Another":"prop","Yes":42}"
Multiple items
namespace DynamicObj

--------------------
type DynamicObj = inherit DynamicObject new: unit -> DynamicObj member CopyDynamicProperties: unit -> DynamicObj member CopyDynamicPropertiesTo: 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
namespace Fable
namespace Fable.Core
Multiple items
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
Multiple items
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 id: int
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
val name: string
Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
val mutable _name: string
val this: Person
property Person.Name: string with get, set
val set: elements: 'T seq -> Set<'T> (requires comparison)
val value: string
val p: Person
member DynamicObj.GetPropertyValue: propertyName: string -> obj
member DynamicObj.SetProperty: propertyName: string * propertyValue: obj -> unit
member DynamicObj.TryGetTypedPropertyValue: propertyName: string -> 'TPropertyValue option
module DynObj from DynamicObj
<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>
val tryGetTypedPropertyValue: propertyName: string -> dynObj: DynamicObj -> 'TPropertyValue option
<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>
val withProperty: propertyName: string -> propertyValue: 'TPropertyValue -> dynObj: 'a -> 'a (requires 'a :> DynamicObj)
<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>
val withoutProperty: propertyName: string -> dynObj: 'a -> 'a (requires 'a :> DynamicObj)
<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>
val withOptionalProperty: propertyName: string -> propertyValue: 'TPropertyValue option -> dynObj: 'a -> 'a (requires 'a :> DynamicObj)
<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>
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val format: dynObj: DynamicObj -> string
<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>
namespace Newtonsoft
namespace Newtonsoft.Json
type JsonConvert = static member DeserializeAnonymousType<'T> : value: string * anonymousTypeObject: 'T -> 'T + 1 overload static member DeserializeObject: value: string -> obj + 7 overloads static member DeserializeXNode: value: string -> XDocument + 3 overloads static member DeserializeXmlNode: value: string -> XmlDocument + 3 overloads static member PopulateObject: value: string * target: obj -> unit + 1 overload static member SerializeObject: value: obj -> string + 7 overloads static member SerializeXNode: node: XObject -> string + 2 overloads static member SerializeXmlNode: node: XmlNode -> string + 2 overloads static member ToString: value: DateTime -> string + 24 overloads static val False: string ...
<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) : string
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

Type something to start searching.