Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix System.Dynamic.DynamicObject based Serialization via Newtonsoft.Json #37

Merged
merged 4 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/build-and-deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup .NET 6
uses: actions/setup-dotnet@v1
uses: actions/checkout@v4
- name: Setup .NET 8
uses: actions/setup-dotnet@v4
with:
dotnet-version: '6.x.x'
dotnet-version: '8.x.x'
- name: make script executable
run: chmod u+x build.sh
- name: Restore tools
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ jobs:
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

# SETUP .NET
- name: Setup .NET
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.x.x
dotnet-version: 8.x.x
- name: Restore fable
run: dotnet tool restore

Expand Down
15 changes: 14 additions & 1 deletion build/BasicTasks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,18 @@ let clean = BuildTask.create "Clean" [] {

let build = BuildTask.create "Build" [clean] {
solutionFile
|> DotNet.build id
|> DotNet.build (fun p ->
let msBuildParams =
{p.MSBuildParams with
Properties = ([
"warnon", "3390"
])
DisableInternalBinLog = true
}
{
p with
MSBuildParams = msBuildParams
}
|> DotNet.Options.withCustomParams (Some "-tl")
)
}
1 change: 1 addition & 0 deletions build/PackageTasks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module BundleDotNet =
"Version",versionTag
"PackageReleaseNotes", (ProjectInfo.release.Notes |> List.map replaceCommitLink |> String.toLines )
] @ p.MSBuildParams.Properties)
DisableInternalBinLog = true
}
{
p with
Expand Down
26 changes: 1 addition & 25 deletions build/TestTasks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,4 @@ let runTests = BuildTask.createEmpty "RunTests" [
(*RunTests.runTestsJsNative; *)
RunTests.runTestsDotnet

]



// to do: use this once we have actual tests
let runTestsWithCodeCov = BuildTask.create "RunTestsWithCodeCov" [clean; build] {
let standardParams = Fake.DotNet.MSBuild.CliArguments.Create ()
testProjects
|> Seq.iter(fun testProject ->
Fake.DotNet.DotNet.test(fun testParams ->
{
testParams with
MSBuildParams = {
standardParams with
Properties = [
"AltCover","true"
"AltCoverCobertura","../../codeCov.xml"
"AltCoverForce","true"
]
};
Logger = Some "console;verbosity=detailed"
}
) testProject
)
}
]
20 changes: 10 additions & 10 deletions build/build.fsproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>

Expand All @@ -19,15 +19,15 @@

<ItemGroup>
<PackageReference Include="BlackFox.Fake.BuildTask" Version="0.1.3" />
<PackageReference Include="Fake.Api.Github" Version="6.0.0" />
<PackageReference Include="Fake.Core.Process" Version="6.0.0" />
<PackageReference Include="Fake.Core.ReleaseNotes" Version="6.0.0" />
<PackageReference Include="Fake.Core.Target" Version="6.0.0" />
<PackageReference Include="Fake.DotNet.Cli" Version="6.0.0" />
<PackageReference Include="Fake.DotNet.MSBuild" Version="6.0.0" />
<PackageReference Include="Fake.IO.FileSystem" Version="6.0.0" />
<PackageReference Include="Fake.JavaScript.Npm" Version="6.0.0" />
<PackageReference Include="Fake.Tools.Git" Version="6.0.0" />
<PackageReference Include="Fake.Api.Github" Version="6.1.1" />
<PackageReference Include="Fake.Core.Process" Version="6.1.1" />
<PackageReference Include="Fake.Core.ReleaseNotes" Version="6.1.1" />
<PackageReference Include="Fake.Core.Target" Version="6.1.1" />
<PackageReference Include="Fake.DotNet.Cli" Version="6.1.1" />
<PackageReference Include="Fake.DotNet.MSBuild" Version="6.1.1" />
<PackageReference Include="Fake.IO.FileSystem" Version="6.1.1" />
<PackageReference Include="Fake.JavaScript.Npm" Version="6.1.1" />
<PackageReference Include="Fake.Tools.Git" Version="6.1.1" />
<PackageReference Include="Fake.Extensions.Release" Version="1.0.0" />

</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "6.0.100",
"version": "8.0.100",
"rollForward": "latestMinor"
}
}
2 changes: 1 addition & 1 deletion src/DynamicObj.Immutable/DynamicObj.Immutable.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

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

<ItemGroup>
Expand Down
41 changes: 35 additions & 6 deletions src/DynamicObj/DynamicObj.fs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
namespace DynamicObj

//open System.Dynamic
#if !FABLE_COMPILER
open System.Dynamic
#endif

open System.Collections.Generic
open Fable.Core

[<AttachMembers>]
type DynamicObj() =
type DynamicObj() =

#if !FABLE_COMPILER
inherit DynamicObject()
#endif

//#if !FABLE_COMPILER
//inherit DynamicObject()
//#endif

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

/// <summary>
Expand Down Expand Up @@ -72,7 +75,7 @@
| Some pi -> Some pi
| None -> this.TryGetDynamicPropertyHelper propertyName

/// <summary>

Check warning on line 78 in src/DynamicObj/DynamicObj.fs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

This XML comment is incomplete: no documentation for parameter 'propertyName'

Check warning on line 78 in src/DynamicObj/DynamicObj.fs

View workflow job for this annotation

GitHub Actions / test (windows-latest)

This XML comment is incomplete: no documentation for parameter 'propertyName'
/// Returns Some(boxed property value) if a dynamic (or static) property with the given name exists, otherwise None.
/// </summary>
/// <param name="name">the name of the property to get</param>
Expand Down Expand Up @@ -247,6 +250,32 @@
this.CopyDynamicPropertiesTo(target)
target

#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 ) =
match this.TryGetPropertyValue binder.Name with
| Some value -> result <- value; true
| None -> false

override this.TrySetMember(binder:SetMemberBinder, value:obj) =
this.SetProperty(binder.Name,value)
true

/// Returns both instance and dynamic member names.
/// 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>()

//[<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.

#endif

/// <summary>
/// Operator to access a property by name
Expand Down
1 change: 0 additions & 1 deletion src/DynamicObj/DynamicObj.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@

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

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>

<IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
Expand Down
4 changes: 3 additions & 1 deletion tests/DynamicObject.Tests/DynamicObject.Tests.fsproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>

<IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
Expand All @@ -13,6 +13,7 @@
<Compile Include="Interface.fs" />
<Compile Include="DynamicObjs.fs" />
<Compile Include="DynObj.fs" />
<Compile Include="Serialization.fs" />
<Compile Include="Main.fs" />
</ItemGroup>

Expand All @@ -31,6 +32,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<ProjectReference Include="..\..\src\DynamicObj\DynamicObj.fsproj" />
</ItemGroup>

Expand Down
1 change: 1 addition & 0 deletions tests/DynamicObject.Tests/Main.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ let all = testSequenced <| testList "DynamicObj" [
DynObj.Tests.main
Inheritance.Tests.main
Interface.Tests.main
Serialization.Tests.main
]

[<EntryPoint>]
Expand Down
106 changes: 106 additions & 0 deletions tests/DynamicObject.Tests/Serialization.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
module Serialization.Tests

open System
open System.Collections.Generic
open Fable.Pyxpecto
open DynamicObj

open DynamicObj

let test_dynobj =
let obj = new DynamicObj()
obj.SetProperty("dynamic_string", "yes")
obj.SetProperty("dynamic_number", 69)
obj.SetProperty("dynamic_boolean", true)
obj.SetProperty("dynamic_array", ["First"; "Second"])
obj.SetProperty(
"dynamic_object",
let tmp = new DynamicObj()
tmp.SetProperty("inner", "yup")
tmp
)
obj

type DerivedClass1(staticProp: string) =
inherit DynamicObj()
member this.StaticProp = staticProp

type DerivedClass2(
staticString: string,
staticNumber: float,
staticBoolean: bool,
staticArray: string list,
staticObject: DynamicObj
) =
inherit DynamicObj()
member this.StaticString = staticString
member this.StaticNumber = staticNumber
member this.StaticBoolean = staticBoolean
member this.StaticArray = staticArray
member this.StaticObject = staticObject

let test_derived_1 =
let obj = DerivedClass1("lol")
obj.SetProperty("dynamicProp", 42)
obj

let test_derived_2 =
let obj = DerivedClass2(
"lol",
42.0,
true,
["First"; "Second"],
test_derived_1
)
obj.SetProperty("dynamic_string", "yes")
obj.SetProperty("dynamic_number", 69)
obj.SetProperty("dynamic_boolean", true)
obj.SetProperty("dynamic_array", ["First"; "Second"])
obj.SetProperty(
"dynamic_object",
let tmp = new DynamicObj()
tmp.SetProperty("inner", "yup")
tmp
)
obj

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

testCase "Serialize simplederived class from DynamicObj" <| fun _ ->
let actual = Newtonsoft.Json.JsonConvert.SerializeObject(test_derived_1)
Expect.equal actual """{"StaticProp":"lol","dynamicProp":42}""" ""

testCase "Serialize complex derived class from DynamicObj" <| fun _ ->
let actual = Newtonsoft.Json.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"}}""" ""
]

let tests_system_text_json = ptestList "System.Text.Json (.NET)" [
testCase "Serialize DynamicObj" <| fun _ ->
let actual = System.Text.Json.JsonSerializer.Serialize(test_dynobj)
Expect.equal actual """{"dynamic_string":"yes","dynamic_number":69,"dynamic_boolean":true,"dynamic_array":["First","Second"],"dynamic_object":{"inner":"yup"}}""" ""

testCase "Serialize simplederived class from DynamicObj" <| fun _ ->
let actual = System.Text.Json.JsonSerializer.Serialize(test_derived_1)
Expect.equal actual """{"StaticProp":"lol","dynamicProp":42}""" ""

testCase "Serialize complex derived class from DynamicObj" <| fun _ ->
let actual = System.Text.Json.JsonSerializer.Serialize(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"}}""" ""
]
#endif

// eventually, we want a transpilable, custom serialization
let tests_custom = testList "Custom" []

let main = testList "Serialization" [
tests_custom
#if !FABLE_COMPILER
tests_newtonsoft
tests_system_text_json
#endif
]
Loading