From 70755cf5d2a62cc97f296f2bebd0ebe8df612855 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Sun, 21 Jun 2026 20:16:22 +0200 Subject: [PATCH] Support BsonDictionaryOptions on F# maps --- CHANGELOG.md | 2 + .../Serializers/FSharpMapSerializer.fs | 30 ++++++- tests/CSharpDataModels/DataModels.cs | 10 +++ .../FSharp.MongoDB.Bson.Tests.fsproj | 3 +- .../FSharpMapDictionaryRepresentationTests.fs | 88 +++++++++++++++++++ 5 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpMapDictionaryRepresentationTests.fs diff --git a/CHANGELOG.md b/CHANGELOG.md index a78da1f..2cca96e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to FSharp.MongoDB are documented in this file. ## [Unreleased] +- Fixed F# map support for `BsonDictionaryOptions`, including `ArrayOfDocuments` coverage. + ## [0.5.0-beta] diff --git a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpMapSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpMapSerializer.fs index 43ab319..e6feadd 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpMapSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpMapSerializer.fs @@ -16,16 +16,20 @@ namespace MongoDB.Bson.Serialization.Serializers open System.Collections.Generic +open MongoDB.Bson.Serialization +open MongoDB.Bson.Serialization.Options open MongoDB.Bson.Serialization.Serializers /// /// Serializer for F# maps. /// -type FSharpMapSerializer<'KeyType, 'ValueType when 'KeyType : comparison and 'KeyType: not null>() = +type FSharpMapSerializer<'KeyType, 'ValueType when 'KeyType : comparison and 'KeyType: not null> + ( + serializer: + DictionaryInterfaceImplementerSerializer, 'KeyType, 'ValueType> + ) = inherit SerializerBase>() - let serializer = DictionaryInterfaceImplementerSerializer>() - override _.Serialize (context, args, mapValue) = let dictValue = Dictionary() mapValue |> Map.iter (fun key value -> dictValue.Add(key, value)) @@ -36,3 +40,23 @@ type FSharpMapSerializer<'KeyType, 'ValueType when 'KeyType : comparison and 'Ke serializer.Deserialize(context, args) |> Seq.map (|KeyValue|) |> Map.ofSeq + + interface IBsonDictionarySerializer with + member _.DictionaryRepresentation = serializer.DictionaryRepresentation + member _.KeySerializer = serializer.KeySerializer :> IBsonSerializer + member _.ValueSerializer = serializer.ValueSerializer :> IBsonSerializer + + interface IDictionaryRepresentationConfigurable with + member _.DictionaryRepresentation = serializer.DictionaryRepresentation + member _.WithDictionaryRepresentation dictionaryRepresentation = + FSharpMapSerializer<'KeyType, 'ValueType>(serializer.WithDictionaryRepresentation(dictionaryRepresentation)) + :> IBsonSerializer + + interface IDictionaryRepresentationConfigurable> with + member _.WithDictionaryRepresentation dictionaryRepresentation = + FSharpMapSerializer<'KeyType, 'ValueType>(serializer.WithDictionaryRepresentation(dictionaryRepresentation)) + + new () = + FSharpMapSerializer<'KeyType, 'ValueType>( + DictionaryInterfaceImplementerSerializer, 'KeyType, 'ValueType>() + ) diff --git a/tests/CSharpDataModels/DataModels.cs b/tests/CSharpDataModels/DataModels.cs index fe7f642..5245fda 100644 --- a/tests/CSharpDataModels/DataModels.cs +++ b/tests/CSharpDataModels/DataModels.cs @@ -1,5 +1,7 @@ namespace CSharpDataModels; using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Options; public record Pair { @@ -40,3 +42,11 @@ public record RecordDataModel public required Dictionary Map { get; init; } } + +public record UserId([property: BsonGuidRepresentation(GuidRepresentation.Standard)] Guid Value); + +public record UserMapDocument +{ + [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)] + public required Dictionary Users { 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 dace268..4a81778 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj +++ b/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj @@ -11,6 +11,7 @@ + @@ -33,4 +34,4 @@ - \ No newline at end of file + diff --git a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpMapDictionaryRepresentationTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpMapDictionaryRepresentationTests.fs new file mode 100644 index 0000000..5984767 --- /dev/null +++ b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpMapDictionaryRepresentationTests.fs @@ -0,0 +1,88 @@ +namespace FSharp.MongoDB.Bson.Tests.Serialization + +open System +open CSharpDataModels +open FsUnit +open MongoDB.Bson +open MongoDB.Bson.Serialization.Attributes +open MongoDB.Bson.Serialization.Options +open NUnit.Framework + +module FSharpMapDictionaryRepresentation = + + [] + type FsUserId = + { [] + Value: Guid } + + [] + type FsUserMapDocument = + { [] + Users: Map } + + let private userGuid = Guid.Parse("550e8400-e29b-41d4-a716-446655440000") + + let private expectedDocument = + BsonDocument( + [ BsonElement( + "Users", + BsonArray( + [ BsonDocument( + [ BsonElement("k", BsonDocument("Value", BsonBinaryData(userGuid, GuidRepresentation.Standard))) + BsonElement("v", BsonString("John")) ]) ])) ]) + + [] + let ``serialize map with complex key as array of documents`` () = + let value = + { Users = + Map [ { Value = userGuid }, "John" ] } + + let result = serialize value + result |> should equal expectedDocument + + [] + let ``deserialize map with complex key from array of documents`` () = + let result = deserialize expectedDocument + + result.Users + |> should equal (Map [ { Value = userGuid }, "John" ]) + + [] + let ``CSharp and FSharp models serialize the same array of documents representation`` () = + let csUsers = + System.Collections.Generic.Dictionary( + dict [ (CSharpDataModels.UserId(userGuid), "John") ]) + + let csValue = + new CSharpDataModels.UserMapDocument(Users = csUsers) + + let fsValue = + { Users = + Map [ { Value = userGuid }, "John" ] } + + let csDoc = serialize csValue + let fsDoc = serialize fsValue + + csDoc |> should equal expectedDocument + fsDoc |> should equal expectedDocument + + [] + let ``cross deserialize map with complex key using array of documents`` () = + let csUsers = + System.Collections.Generic.Dictionary( + dict [ (CSharpDataModels.UserId(userGuid), "John") ]) + + let csDoc = + serialize (new CSharpDataModels.UserMapDocument(Users = csUsers)) + + let fsValue = deserialize csDoc + fsValue.Users |> should equal (Map [ { Value = userGuid }, "John" ]) + + let fsDoc = + serialize + { Users = + Map [ { Value = userGuid }, "John" ] } + + let csValue = deserialize fsDoc + csValue.Users.Count |> should equal 1 + csValue.Users[CSharpDataModels.UserId(userGuid)] |> should equal "John"