-
Type: Bug
-
Resolution: Works as Designed
-
Priority: Major - P3
-
None
-
Affects Version/s: 2.9.3
-
Component/s: BSON
-
None
Below is a repro (in an xUnit test method) of a case where a custom serializer is being registered properly and used for serialization, but isn't getting used for deserialization:
Unable to find source-code formatter for language: csharp. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
namespace Test { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using MongoDB.Bson; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Options; using MongoDB.Bson.Serialization.Serializers; using OBeautifulCode.Serialization.Bson; using Xunit; public static class CustomSerializerTest { [Fact] public static void Test() { // setup the serializer var classMap = new BsonClassMap<MyTestModel>(); var memberInfo = typeof(MyTestModel).GetMember(nameof(MyTestModel.MyProperty)).Single(); var memberMap = classMap.MapMember(memberInfo); var keySerializer = new MyDateTimeSerializer(); var valueSerializer = new StringSerializer(); var dictionarySerializer = new MyDictionarySerializer<IReadOnlyDictionary<DateTime, string>, DateTime, string>( DictionaryRepresentation.ArrayOfDocuments, keySerializer, valueSerializer); var serializer = new MyListSerializer<IReadOnlyList<IReadOnlyDictionary<DateTime, string>>, IReadOnlyDictionary<DateTime, string>>(dictionarySerializer); memberMap.SetSerializer(serializer); BsonClassMap.RegisterClassMap(classMap); var expected = new MyTestModel { MyProperty = new List<IReadOnlyDictionary<DateTime, string>> { new Dictionary<DateTime, string> { {DateTime.Now, "whatever"}, }, }, }; // serialize var document = new BsonDocument(); using (var writer = new BsonDocumentWriter(document)) { BsonSerializer.Serialize(writer, expected.GetType(), expected); writer.Close(); } // prove that our serializers are being called // you can also put breakpoints in all the Serialize methods and run in the debugger // all the breakpoints will be hit. var actualJson = document.ToJson(); var expectedJson = "{ \"_t\" : \"MyTestModel\", \"MyProperty\" : { \"_t\" : \"ReadOnlyCollection`1\", \"_v\" : [[{ \"k\" : \"does-not-matter\", \"v\" : \"whatever\" }]] } }"; Assert.Equal(expectedJson, actualJson); // de-serialize. throws FormatException ("String was not recognized as a valid DateTime") // proves that MyDateTimeSerializer is NOT being called. you can also put breakpoints // in all of the Deserialize methods and run in debugger - it hit MyListSerializer // but doesn't hit MyDictionarySerializer ObcBsonSerializerHelper.DeserializeFromDocument<MyTestModel>(document); } private class MyTestModel { public IReadOnlyList<IReadOnlyDictionary<DateTime, string>> MyProperty { get; set; } } private class MyListSerializer<TCollection, TElement> : SerializerBase<TCollection> where TCollection : class, IEnumerable<TElement> { private readonly ReadOnlyCollectionSerializer<TElement> underlyingSerializer; public MyListSerializer(IBsonSerializer<TElement> elementSerializer) { this.underlyingSerializer = new ReadOnlyCollectionSerializer<TElement>(elementSerializer); } /// <inheritdoc /> public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TCollection value) { if (value == null) { context.Writer.WriteNull(); return; } this.underlyingSerializer.Serialize(context, args, new ReadOnlyCollection<TElement>(value.ToList())); } /// <inheritdoc /> public override TCollection Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { if (context.Reader.State != BsonReaderState.Type && context.Reader.CurrentBsonType == BsonType.Null) { context.Reader.ReadNull(); return null; } var collection = this.underlyingSerializer.Deserialize(context, args); var result = collection.ToList() as TCollection; return result; } } private class MyDictionarySerializer<TDictionary, TKey, TValue> : SerializerBase<TDictionary> where TDictionary : class, IEnumerable<KeyValuePair<TKey, TValue>> { private readonly DictionaryInterfaceImplementerSerializer<Dictionary<TKey, TValue>> underlyingSerializer; public MyDictionarySerializer(DictionaryRepresentation dictionaryRepresentation, IBsonSerializer keySerializer, IBsonSerializer valueSerializer) { this.underlyingSerializer = new DictionaryInterfaceImplementerSerializer<Dictionary<TKey, TValue>>(dictionaryRepresentation, keySerializer, valueSerializer); } /// <inheritdoc /> public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TDictionary value) { if (value == null) { context.Writer.WriteNull(); return; } this.underlyingSerializer.Serialize(context, args, ((IDictionary<TKey, TValue>) value).ToDictionary(_ => _.Key, _ => _.Value)); } /// <inheritdoc /> public override TDictionary Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { if ((context.Reader.State != BsonReaderState.Type) && (context.Reader.CurrentBsonType == BsonType.Null)) { context.Reader.ReadNull(); return null; } var dictionary = this.underlyingSerializer.Deserialize(context, args); var result = new ReadOnlyDictionary<TKey, TValue>(dictionary) as TDictionary; return result; } } private class MyDateTimeSerializer : SerializerBase<DateTime> { private const string DoesNotMatter = "does-not-matter"; /// <inheritdoc /> public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTime value) { context.Writer.WriteString(DoesNotMatter); } /// <inheritdoc /> public override DateTime Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { var type = context.Reader.GetCurrentBsonType(); if ((type == BsonType.String) && (context.Reader.ReadString() == DoesNotMatter)) { return DateTime.Now; } throw new NotSupportedException(); } } } }
- related to
-
CSHARP-2877 Default BsonSerializationArgs sometimes has null NominalType
- Closed