-
Type:
Bug
-
Resolution: Works as Designed
-
Priority:
Major - P3
-
None
-
Affects Version/s: 2.9.3
-
Component/s: BSON
-
None
-
None
-
None
-
None
-
None
-
None
-
None
-
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
-