diff --git a/.github/workflows/on-push-branch.yml b/.github/workflows/on-push-branch.yml index 0283d2e..ff664b3 100644 --- a/.github/workflows/on-push-branch.yml +++ b/.github/workflows/on-push-branch.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.100 + dotnet-version: 9.0.203 - name: Build & Test run: make test config=Release diff --git a/.github/workflows/on-push-tag.yml b/.github/workflows/on-push-tag.yml index ad59115..06113c0 100644 --- a/.github/workflows/on-push-tag.yml +++ b/.github/workflows/on-push-tag.yml @@ -24,7 +24,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.100 + dotnet-version: 9.0.203 - name: Extract Version Suffix run: | diff --git a/.github/workflows/on-release-published.yml b/.github/workflows/on-release-published.yml index c58be85..d44a377 100644 --- a/.github/workflows/on-release-published.yml +++ b/.github/workflows/on-release-published.yml @@ -13,7 +13,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.100 + dotnet-version: 9.0.203 - name: Download Release artifacts uses: robinraju/release-downloader@v1.11 diff --git a/Makefile b/Makefile index 159f988..ef22016 100644 --- a/Makefile +++ b/Makefile @@ -10,3 +10,6 @@ test: nuget: dotnet pack -c $(config) -p:Version=$(version) -o .out + +upgrade: + dotnet restore --force-evaluate diff --git a/README.md b/README.md index 8b6463d..0e37cea 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,9 @@ Repository origins are: ## Supported platforms -This project targets `netstandard2.1` ([compatible runtimes](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-1#select-net-standard-version)). +This project targets `.net 8`, `.net 9` and `netstandard2.1` ([compatible runtimes](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-1#select-net-standard-version)) + +:warning: NRT support starts with `.net sdk 9.0.200`. F# compiler in .net sdk 9.0.10x does not set correctly nullable attributes on F# types. NRT are not supported on `netstandard2.1`. ## Contributing * If you have a question about the library, then create an [issue][issues] with the `question` label. @@ -89,7 +91,9 @@ NRT are serialized as: * `null` if `Null` * `object` if `NonNull` object -:warning: As of now, NRT can't be considered `null` when deserializing if value is missing. +On deserialization, missing value is mapped to `null`. + +:warning: NRT support starts with .net sdk 9.0.200. F# compiler in .net sdk 9.0.10x does not set correctly nullable attributes on F# types. # License The contents of this library are made available under the [Apache License, Version 2.0][license]. diff --git a/global.json b/global.json new file mode 100644 index 0000000..79790f9 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "9.0.203" + } + } \ No newline at end of file diff --git a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj index 5480523..13e2a40 100644 --- a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj +++ b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj @@ -1,7 +1,7 @@ - + - net9.0;netstandard2.1 + net9.0;net8.0;netstandard2.1 true enable true @@ -26,7 +26,7 @@ - + diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs index 60f228c..dc1f900 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs @@ -31,12 +31,12 @@ type FSharpRecordConvention() = match classMap.ClassType with | IsRecord typ -> let fields = FSharpType.GetRecordFields(typ, bindingFlags) - let names = fields |> Array.map (fun x -> x.Name) + let names = fields |> Array.map _.Name // Map the constructor of the record type. let ctor = FSharpValue.PreComputeRecordConstructorInfo(typ, bindingFlags) classMap.MapConstructor(ctor, names) |> ignore // Map each field of the record type. - fields |> Array.iter (classMap.MapMember >> ignore) + fields |> Array.iter (mapMemberNullable classMap) | _ -> () diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs index fbd1112..fbc458d 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs @@ -65,7 +65,7 @@ type UnionCaseConvention() = let mapUnionCase (classMap:BsonClassMap) (unionCase:UnionCaseInfo) = let fields = unionCase.GetFields() - let names = fields |> Array.map (fun x -> x.Name) + let names = fields |> Array.map _.Name classMap.SetDiscriminator unionCase.Name classMap.SetDiscriminatorIsRequired true @@ -76,7 +76,7 @@ type UnionCaseConvention() = classMap.MapCreator(del, names) |> ignore // Map each field of the union case. - fields |> Array.iter (classMap.MapMember >> ignore) + fields |> Array.iter (mapMemberNullable classMap) interface IClassMapConvention with member _.Apply classMap = diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs index 09104f1..2aa8c57 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs @@ -23,6 +23,10 @@ module private Helpers = let bindingFlags = BindingFlags.Public ||| BindingFlags.NonPublic +#if !NETSTANDARD2_1 + let nrtContext = NullabilityInfoContext() +#endif + /// /// Returns Some typ when pred typ returns true, and None when /// pred typ returns false. @@ -90,3 +94,16 @@ module private Helpers = /// Creates a generic type 'T using the generic arguments of typ. /// let mkGenericUsingDef<'T> (typ:System.Type) = typ.GetGenericArguments() |> mkGeneric<'T> + + /// + /// Maps a member of a BsonClassMap to a nullable value if possible. + /// + let mapMemberNullable (classMap: BsonClassMap) (propertyInfo: PropertyInfo) = + let memberMap = classMap.MapMember(propertyInfo) +#if !NETSTANDARD2_1 + let nrtInfo = nrtContext.Create(propertyInfo) + if nrtInfo.ReadState = NullabilityState.Nullable then + memberMap.SetDefaultValue(objnull) |> ignore +#else + memberMap.SetDefaultValue(objnull) |> ignore +#endif diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs index 3aae692..6fed8af 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs @@ -33,7 +33,7 @@ module FSharp = member _.GetSerializer typ = let mkSerializer typ = typ - |> Option.map (fun typ -> System.Activator.CreateInstance typ :?> IBsonSerializer) + |> Option.map (fun typ -> System.Activator.CreateInstance typ |> nonNull :?> IBsonSerializer) |> Option.toObj match typ with diff --git a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpOptionSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpOptionSerializer.fs index 4a8752e..ad1a836 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpOptionSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpOptionSerializer.fs @@ -40,4 +40,4 @@ type FSharpOptionSerializer<'T when 'T: not null>() = match reader.GetCurrentBsonType() with | BsonType.Null -> reader.ReadNull(); None - | _ -> Some (serializer.Value.Deserialize(context, args)) + | t -> Some (serializer.Value.Deserialize(context, args)) diff --git a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpUnionSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpUnionSerializer.fs index 41ba739..ab0c60e 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpUnionSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpUnionSerializer.fs @@ -38,7 +38,7 @@ type FSharpUnionSerializer<'T>() = let mkClassMapSerializer (caseType:System.Type) = let classMap = BsonClassMap.LookupClassMap caseType let serializerType = typedefof>.MakeGenericType [| caseType |] - System.Activator.CreateInstance(serializerType, classMap) :?> IBsonSerializer + System.Activator.CreateInstance(serializerType, classMap) |> nonNull :?> IBsonSerializer // 8.5.4. Compiled Form of Union Types for Use from Other CLI Languages // A compiled union type U has [o]ne CLI nested type U.C for each non-null union case C. diff --git a/tests/CSharpDataModels/CSharpDataModels.csproj b/tests/CSharpDataModels/CSharpDataModels.csproj index 4bd36d5..737ad2b 100644 --- a/tests/CSharpDataModels/CSharpDataModels.csproj +++ b/tests/CSharpDataModels/CSharpDataModels.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -8,7 +8,7 @@ - + diff --git a/tests/CSharpDataModels/DataModels.cs b/tests/CSharpDataModels/DataModels.cs index fe7f642..5cf93dd 100644 --- a/tests/CSharpDataModels/DataModels.cs +++ b/tests/CSharpDataModels/DataModels.cs @@ -18,25 +18,25 @@ public record PairValue(Pair Value) : Value; public record RecordDataModel { - public ObjectId Id { get; init; } - - public required int Int { get; init; } - public int? IntOpt { get; init; } - - public required string String { get; init; } - public string? StringOpt { get; init; } - - public required int[] Array { get; init; } - public int[]? ArrayOpt { get; init; } - - public required Value Value { get; init; } - public Value? ValueOpt { get; init; } - - public required Value[] ValueArray { get; init; } - public Value[]? ValueArrayOpt { get; init; } - - public required Pair Record { get; init; } + // public ObjectId Id { get; init; } + // + // public required int Int { get; init; } + // public int? IntOpt { get; init; } + // + // public required string String { get; init; } + // public string? StringOpt { get; init; } + // + // public required int[] Array { get; init; } + // public int[]? ArrayOpt { get; init; } + + // public required Value Value { get; init; } + // public Value? ValueOpt { get; init; } + + // public required Value[] ValueArray { get; init; } + // public Value[]? ValueArrayOpt { get; init; } + + // public required Pair Record { get; init; } public Pair? RecordOpt { get; init; } - public required Dictionary Map { get; init; } + // public required Dictionary Map { get; init; } } diff --git a/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj b/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj index 3795dcc..5c86b75 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj +++ b/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj @@ -1,4 +1,4 @@ - + net9.0 @@ -20,20 +20,17 @@ - - - - - - - - + + + + + \ No newline at end of file diff --git a/tests/FSharp.MongoDB.Bson.Tests/Isomorphic/IsomorphicTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Isomorphic/IsomorphicTests.fs index bcc4d35..3fd0d0e 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/Isomorphic/IsomorphicTests.fs +++ b/tests/FSharp.MongoDB.Bson.Tests/Isomorphic/IsomorphicTests.fs @@ -19,27 +19,29 @@ module IsomorphicSerialization = [] type RecordDataModel = - { Id: ObjectId + { + // Id: ObjectId - Int: int - IntOpt: int option + // Int: int + // IntOpt: int option + // + // String: string + // StringOpt: string option + // + // Array: int array + // ArrayOpt: int array option + // + // Value: Value + // ValueOpt: Value option - String: string - StringOpt: string option + // ValueArray: Value array + // ValueArrayOpt: Value array option - Array: int array - ArrayOpt: int array option - - Value: Value - ValueOpt: Value option - - ValueArray: Value array - ValueArrayOpt: Value array option - - Record: Pair + // Record: Pair RecordOpt: Pair option - Map: Map } + // Map: Map + } let ModelSome() = let csModel = @@ -50,38 +52,41 @@ module IsomorphicSerialization = map RecordDataModel( - Id = ObjectId.GenerateNewId(), - Int = 42, - IntOpt = 666, - String = "String", - StringOpt = "StringOpt", - Array = [| 1; 2; 3 |], - ArrayOpt = [| 5; 6; 7; 8 |], - Value = CSharpDataModels.Value.IntValue(42), - ValueOpt = CSharpDataModels.Value.StringValue("ValueStringOpt"), - ValueArray = [| CSharpDataModels.Value.IntValue(42) - CSharpDataModels.Value.StringValue("String") - CSharpDataModels.Value.PairValue(CSharpDataModels.Pair(First = 99, Second = "SecondPair")) |], - ValueArrayOpt = [| CSharpDataModels.Value.IntValue(101) |], - Record = CSharpDataModels.Pair(First = 1, Second = "Second"), - RecordOpt = CSharpDataModels.Pair(First = -1, Second = "SecondOpt"), - Map = map) + // Id = ObjectId.GenerateNewId(), + // Int = 42, + // IntOpt = 666, + // String = "String", + // StringOpt = "StringOpt", + // Array = [| 1; 2; 3 |], + // ArrayOpt = [| 5; 6; 7; 8 |], + // Value = CSharpDataModels.Value.IntValue(42), + // ValueOpt = CSharpDataModels.Value.StringValue("ValueStringOpt"), + // ValueArray = [| CSharpDataModels.Value.IntValue(42) + // CSharpDataModels.Value.StringValue("String") + // CSharpDataModels.Value.PairValue(CSharpDataModels.Pair(First = 99, Second = "SecondPair")) |], + // ValueArrayOpt = [| CSharpDataModels.Value.IntValue(101) |], + // Record = CSharpDataModels.Pair(First = 1, Second = "Second"), + RecordOpt = CSharpDataModels.Pair(First = -1, Second = "SecondOpt") + // Map = map + ) let fsModel = - { Id = csModel.Id - Int = 42 - IntOpt = Some 666 - String = "String" - StringOpt = Some "StringOpt" - Array = [| 1; 2; 3 |] - ArrayOpt = Some [| 5; 6; 7; 8 |] - Value = Value.IntValue 42 - ValueOpt = Some <| Value.StringValue "ValueStringOpt" - ValueArray = [| Value.IntValue 42; Value.StringValue "String"; Value.PairValue { First = 99; Second = Some "SecondPair" } |] - ValueArrayOpt = Some [| Value.IntValue 101 |] - Record = { First = 1; Second = Some "Second" } + { + // Id = csModel.Id + // Int = 42 + // IntOpt = Some 666 + // String = "String" + // StringOpt = Some "StringOpt" + // Array = [| 1; 2; 3 |] + // ArrayOpt = Some [| 5; 6; 7; 8 |] + // Value = Value.IntValue 42 + // ValueOpt = Some <| Value.StringValue "ValueStringOpt" + // ValueArray = [| Value.IntValue 42; Value.StringValue "String"; Value.PairValue { First = 99; Second = Some "SecondPair" } |] + // ValueArrayOpt = Some [| Value.IntValue 101 |] + // Record = { First = 1; Second = Some "Second" } RecordOpt = Some { First = -1; Second = Some "SecondOpt" } - Map = Map [ "1", 1; "2", 2 ] } + // Map = Map [ "1", 1; "2", 2 ] + } csModel, fsModel @@ -94,32 +99,35 @@ module IsomorphicSerialization = map RecordDataModel( - Id = ObjectId.GenerateNewId(), - Int = 42, - String = "String", - Array = [| 1; 2; 3 |], - Value = CSharpDataModels.Value.IntValue(42), - ValueArray = [| CSharpDataModels.Value.IntValue(42) - CSharpDataModels.Value.StringValue("String") - CSharpDataModels.Value.PairValue(CSharpDataModels.Pair(First = 99, Second = "SecondPair")) |], - Record = CSharpDataModels.Pair(First = 1, Second = "Second"), - Map = map) + // Id = ObjectId.GenerateNewId(), + // Int = 42, + // String = "String", + // Array = [| 1; 2; 3 |], + // Value = CSharpDataModels.Value.IntValue(42), + // ValueArray = [| CSharpDataModels.Value.IntValue(42) + // CSharpDataModels.Value.StringValue("String") + // CSharpDataModels.Value.PairValue(CSharpDataModels.Pair(First = 99, Second = "SecondPair")) |], + // Record = CSharpDataModels.Pair(First = 1, Second = "Second"), + // Map = map + ) let fsModel = - { Id = csModel.Id - Int = 42 - IntOpt = None - String = "String" - StringOpt = None - Array = [| 1; 2; 3 |] - ArrayOpt = None - Value = Value.IntValue 42 - ValueOpt = None - ValueArray = [| Value.IntValue 42; Value.StringValue "String"; Value.PairValue { First = 99; Second = Some "SecondPair" } |] - ValueArrayOpt = None - Record = { First = 1; Second = Some "Second" } + { + // Id = csModel.Id + // Int = 42 + // IntOpt = None + // String = "String" + // StringOpt = None + // Array = [| 1; 2; 3 |] + // ArrayOpt = None + // Value = Value.IntValue 42 + // ValueOpt = None + // ValueArray = [| Value.IntValue 42; Value.StringValue "String"; Value.PairValue { First = 99; Second = Some "SecondPair" } |] + // ValueArrayOpt = None + // Record = { First = 1; Second = Some "Second" } RecordOpt = None - Map = Map [ "1", 1; "2", 2 ] } + // Map = Map [ "1", 1; "2", 2 ] + } csModel, fsModel @@ -127,16 +135,23 @@ module IsomorphicSerialization = let ``Isomorphic Bson Some cs / fs``() = let csModel, fsModel = ModelSome() + // serialization shall be same let csDoc = serialize csModel let fsDoc = serialize fsModel - csDoc |> should equal fsDoc + // cross-deserialization shall work + let fsModel2 = deserialize csDoc + fsModel2 |> should equal fsModel + [] let ``Isomorphic Bson None cs / fs``() = let csModel, fsModel = ModelNone() let csDoc = serialize csModel let fsDoc = serialize fsModel - csDoc |> should equal fsDoc + + // cross-deserialization shall work + let fsModel2 = deserialize csDoc + fsModel2 |> should equal fsModel diff --git a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs index 4720c17..c682849 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs +++ b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs @@ -20,44 +20,97 @@ open NUnit.Framework module FSharpNRTSerialization = + type UnionCase = + | Tuple of Int:int * String:(string | null) + + type UnionCaseNotNull = + | TupleNotNull of Int:int * String:string + type Primitive = - { String : string | null } + { String : string | null + UnionCase: UnionCase + Int: int } + + type RecordNoNull = + { StringNotNull : string + Int: int } + + type UnionCaseNoNull = + { UnionCaseNotNull : UnionCaseNotNull + Int: int } + [] let ``test serialize nullable reference (null) in a record type``() = - let value = { String = null } + let value = { String = null + UnionCase = Tuple(Int = 42, String = null) + Int = 42 } let result = serialize value - let expected = BsonDocument([ BsonElement("String", BsonNull.Value) ]) + let expected = BsonDocument([ BsonElement("String", BsonNull.Value) + BsonElement("UnionCase", BsonDocument([ BsonElement("_t", BsonString "Tuple") + BsonElement("Int", BsonInt32 42) + BsonElement("String", BsonNull.Value) ])) + BsonElement("Int", BsonInt32 42) ]) result |> should equal expected [] let ``test deserialize nullable reference (null) in a record type)``() = - // FIXME: this shall support deserializing missing null value for NRT - // as of now this means NRT can't be a missing value while deserializing - // let doc = BsonDocument() - let doc = BsonDocument([ BsonElement("String", BsonNull.Value) ]) + // FIXME: once .net 9.0.200 is released, String can be omitted + let doc = BsonDocument([ BsonElement("String", BsonNull.Value) + BsonElement("UnionCase", BsonDocument([ BsonElement("_t", BsonString "Tuple") + BsonElement("Int", BsonInt32 42) + BsonElement("String", BsonNull.Value) ])) + BsonElement("Int", BsonInt32 42) ]) let result = deserialize doc - let expected = { String = null } + let expected = { String = null + UnionCase = Tuple(Int = 42, String = null) + Int = 42 } result |> should equal expected [] let ``test serialize nullable reference (some) in a record type``() = - let value = { String = "A String" } + let value = { String = "A String" + UnionCase = Tuple(Int = 42, String = "Another String") + Int = 42 } let result = serialize value - let expected = BsonDocument([ BsonElement("String", BsonString "A String") ]) + let expected = BsonDocument([ BsonElement("String", BsonString "A String") + BsonElement("UnionCase", BsonDocument([ BsonElement("_t", BsonString "Tuple") + BsonElement("Int", BsonInt32 42) + BsonElement("String", BsonString "Another String") ])) + BsonElement("Int", BsonInt32 42) ]) result |> should equal expected [] let ``test deserialize nullable reference (some) in a record type``() = - let doc = BsonDocument([ BsonElement("String", BsonString "A String") ]) + let doc = BsonDocument([ BsonElement("String", BsonString "A String") + BsonElement("UnionCase", BsonDocument([ BsonElement("_t", BsonString "Tuple") + BsonElement("Int", BsonInt32 42) + BsonElement("String", BsonString "Another String") ])) + BsonElement("Int", BsonInt32 42) ]) let result = deserialize doc - let expected = { String = "A String" } + let expected = { String = "A String" + UnionCase = Tuple(Int = 42, String = "Another String") + Int = 42 } result |> should equal expected + + [] + let ``test deserialize with missing non-null reference in record shall fail``() = + let doc = BsonDocument([ BsonElement("Int", BsonInt32 42) ]) + + (fun () -> deserialize doc |> ignore) + |> should throw typeof + + [] + let ``test deserialize with missing non-null reference in union case shall fail``() = + let doc = BsonDocument([ BsonElement("Int", BsonInt32 42) ]) + + (fun () -> deserialize doc |> ignore) + |> should throw typeof diff --git a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpOptionSerializationTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpOptionSerializationTests.fs index 0e76e8c..f6097f0 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpOptionSerializationTests.fs +++ b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpOptionSerializationTests.fs @@ -21,24 +21,30 @@ open NUnit.Framework module FSharpOptionSerialization = + type Record = + { Value: string } + type Primitive = { Bool : bool option Int : int option String : string option - Float : float option } + Float : float option + Record: Record option } [] let ``test serialize optional primitives (none) in a record type``() = let value = { Bool = None Int = None String = None - Float = None } + Float = None + Record = None } let result = serialize value let expected = BsonDocument([ BsonElement("Bool", BsonNull.Value) BsonElement("Int", BsonNull.Value) BsonElement("String", BsonNull.Value) - BsonElement("Float", BsonNull.Value) ]) + BsonElement("Float", BsonNull.Value) + BsonElement("Record", BsonNull.Value) ]) result |> should equal expected @@ -50,7 +56,8 @@ module FSharpOptionSerialization = let expected = { Bool = None Int = None String = None - Float = None } + Float = None + Record = None } result |> should equal expected @@ -59,13 +66,15 @@ module FSharpOptionSerialization = let value = { Bool = Some false Int = Some 0 String = Some "0.0" - Float = Some 0.0 } + Float = Some 0.0 + Record = Some { Value = "value" } } let result = serialize value let expected = BsonDocument([ BsonElement("Bool", BsonBoolean false) BsonElement("Int", BsonInt32 0) BsonElement("String", BsonString "0.0") - BsonElement("Float", BsonDouble 0.0) ]) + BsonElement("Float", BsonDouble 0.0) + BsonElement("Record", BsonDocument([ BsonElement("Value", "value") ])) ]) result |> should equal expected @@ -74,12 +83,14 @@ module FSharpOptionSerialization = let doc = BsonDocument([ BsonElement("Bool", BsonBoolean true) BsonElement("Int", BsonInt32 1) BsonElement("String", BsonString "1.0") - BsonElement("Float", BsonDouble 1.0) ]) + BsonElement("Float", BsonDouble 1.0) + BsonElement("Record", BsonDocument([ BsonElement("Value", "value") ])) ]) let result = deserialize doc let expected = { Bool = Some true Int = Some 1 String = Some "1.0" - Float = Some 1.0 } + Float = Some 1.0 + Record = Some { Value = "value" } } result |> should equal expected