Skip to content

Commit

Permalink
wip on deserialization
Browse files Browse the repository at this point in the history
  • Loading branch information
kMutagene committed Sep 24, 2024
1 parent 27e0ac4 commit 02f4b1e
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 21 deletions.
41 changes: 34 additions & 7 deletions src/DynamicObj/DynamicObj.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type DynamicObj() =
#if !FABLE_COMPILER
inherit DynamicObject()
#endif

let mutable properties = new Dictionary<string, obj>()

/// <summary>
Expand Down Expand Up @@ -272,13 +272,40 @@ type DynamicObj() =
/// Important to return both so JSON serialization with Json.NET works.
override this.GetDynamicMemberNames() = this.GetPropertyNames(true)

//// potential deserialization support
//[<JsonExtensionData>]
//member private this._additionalData : IDictionary<string, JToken> = new Dictionary<string, JToken>()
// potential deserialization support
[<JsonExtensionData(WriteData = false)>]
member private this.AdditionalProperties : IDictionary<string, JToken> = new Dictionary<string, JToken>()

[<OnDeserialized>]
member private this.OnDeserialized(context:StreamingContext) =
let rec processToken (token: JToken) : obj =
match token.Type with
| JTokenType.Object ->
let dict = DynamicObj()
let jobject = token :?> JObject
for property in jobject.Properties() do
dict.SetProperty(property.Name, processToken(property.Value))
box dict
| JTokenType.Array ->
let array = token :?> JArray
let res =
array
|> Seq.cast<JToken>
|> Seq.toArray
|> Array.map processToken
box res
| JTokenType.Integer -> box (token.ToObject<int>())
| JTokenType.Float -> box (token.ToObject<float>())
| JTokenType.String -> box (token.ToObject<string>())
| JTokenType.Boolean -> box (token.ToObject<bool>())
| JTokenType.Date -> box (token.ToObject<System.DateTime>())
| JTokenType.Null -> null
| _ -> failwith "Unsupported JToken type"

//[<OnDeserialized>]
//member private this.OnDeserialized(context:StreamingContext) = ()
// map over key value pairs in additional data, box the token values and set dynamic properties via SetProperty.
for kv in this.AdditionalProperties do
let propertyName = kv.Key
let propertValue = kv.Value
this.SetProperty(propertyName, processToken propertValue)

#endif

Expand Down
6 changes: 1 addition & 5 deletions src/DynamicObj/DynamicObj.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,12 @@
<Compile Include="DynamicObj.fs" />
<Compile Include="DynObj.fs" />
<None Include="Playground.fsx" />
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Fable.Core" Version="4.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

<ItemGroup>
<Content Include="*.fsproj; **\*.fs; **\*.fsi" PackagePath="fable\" />
</ItemGroup>

</Project>
40 changes: 31 additions & 9 deletions tests/DynamicObject.Tests/Serialization.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ let test_dynobj =
)
obj

let test_dyn_obj_json = """{"dynamic_string":"yes","dynamic_number":69,"dynamic_boolean":true,"dynamic_array":["First","Second"],"dynamic_object":{"inner":"yup"}}"""

type DerivedClass1(staticProp: string) =
inherit DynamicObj()
member this.StaticProp = staticProp
Expand All @@ -47,6 +49,8 @@ let test_derived_1 =
let obj = DerivedClass1("lol")
obj.SetProperty("dynamicProp", 42)
obj

let test_derived_1_json = """{"StaticProp":"lol","dynamicProp":42}"""

let test_derived_2 =
let obj = DerivedClass2(
Expand All @@ -68,19 +72,37 @@ let test_derived_2 =
)
obj

let test_derived_2_json = """{"StaticString":"lol","StaticNumber":42.0,"StaticBoolean":true,"StaticArray":["First","Second"],"StaticObject":{"StaticProp":"lol","dynamicProp":42},"dynamic_string":"yes","dynamic_number":69,"dynamic_boolean":true,"dynamic_array":["First","Second"],"dynamic_object":{"inner":"yup"}}"""

#if !FABLE_COMPILER
let tests_newtonsoft = testList "Newtonsoft (.NET)" [
testCase "Serialize DynamicObj" <| fun _ ->
let actual = JsonConvert.SerializeObject(test_dynobj)
Expect.equal actual """{"dynamic_string":"yes","dynamic_number":69,"dynamic_boolean":true,"dynamic_array":["First","Second"],"dynamic_object":{"inner":"yup"}}""" ""
testList "Serialization" [
testCase "Serialize DynamicObj" <| fun _ ->
let actual = JsonConvert.SerializeObject(test_dynobj)
Expect.equal actual test_dyn_obj_json ""

testCase "Serialize simple derived class from DynamicObj" <| fun _ ->
let actual = JsonConvert.SerializeObject(test_derived_1)
Expect.equal actual test_derived_1_json ""

testCase "Serialize complex derived class from DynamicObj" <| fun _ ->
let actual = JsonConvert.SerializeObject(test_derived_2)
Expect.equal actual test_derived_2_json ""
]
ftestList "Deserialization" [
testCase "Deserialize DynamicObj" <| fun _ ->
let actual = JsonConvert.DeserializeObject<DynamicObj>(test_dyn_obj_json)

printfn "expected props:"
for prop in test_dynobj.GetProperties(true) do
printfn $"{prop.Key}, {prop.Value}, {prop.Value.GetType()}"

testCase "Serialize simplederived class from DynamicObj" <| fun _ ->
let actual = JsonConvert.SerializeObject(test_derived_1)
Expect.equal actual """{"StaticProp":"lol","dynamicProp":42}""" ""
printfn "actual props:"
for prop in actual.GetProperties(true) do
printfn $"{prop.Key}, {prop.Value}, {prop.Value.GetType()}"

testCase "Serialize complex derived class from DynamicObj" <| fun _ ->
let actual = JsonConvert.SerializeObject(test_derived_2)
Expect.equal actual """{"StaticString":"lol","StaticNumber":42.0,"StaticBoolean":true,"StaticArray":["First","Second"],"StaticObject":{"StaticProp":"lol","dynamicProp":42},"dynamic_string":"yes","dynamic_number":69,"dynamic_boolean":true,"dynamic_array":["First","Second"],"dynamic_object":{"inner":"yup"}}""" ""
Expect.equal (actual.GetHashCode()) (test_dynobj.GetHashCode()) ""
]
]
#endif

Expand Down

0 comments on commit 02f4b1e

Please sign in to comment.