Dictionary-to-BsonDocument conversion omit

2019-01-19 02:10发布

问题:

I'm using ToBsonDocument extension method from MongoDB.Bson to convert this Dictionary:

        var dictionary = new Dictionary<string, object> {{"person", new Dictionary<string, object> {{"name", "John"}}}};
        var document = dictionary.ToBsonDocument();

And here's the resulting document:

  { "person" : 
      { "_t" : "System.Collections.Generic.Dictionary`2[System.String,System.Object]", 
        "_v" : { "name" : "John" } } }

Is there a way to get rid of these _t/_v stuff? I would like the resulting document to look like this:

  { "person" : { "name" : "John" } }

UPD: I've found the code in DictionaryGenericSerializer:

if (nominalType == typeof(object))
{
    var actualType = value.GetType();
    bsonWriter.WriteStartDocument();
    bsonWriter.WriteString("_t", TypeNameDiscriminator.GetDiscriminator(actualType));
    bsonWriter.WriteName("_v");
    Serialize(bsonWriter, actualType, value, options); // recursive call replacing nominalType with actualType
    bsonWriter.WriteEndDocument();
    return;
}

So, it seems that there are not too many options with this serializer when the value type is object.

回答1:

You shall first serialize to JSON and then to BSON,

var jsonDoc = Newtonsoft.Json.JsonConvert.SerializeObject(dictionary);
var bsonDoc = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(jsonDoc);


回答2:

That is happen because you specify object type for dictionary values, but actually use Dictionary<string, object> type for the particular record value. Therefore, CSharp driver saves the full name of concrete type to deserialize this document properly in future. You can also read more about it here: Serialize Documents with the CSharp Driver: Polymorphic Classes and Discriminators

To achieve desired result you should specify concrete type for dictionary values:

var dictionary = new Dictionary<string, Dictionary<string, object>>
{
    { "person", new Dictionary<string, object> { { "name", "John" } } }
};
var document = dictionary.ToBsonDocument();


回答3:

A straight forward conversion from object to BSON is difficult.

Instead go from object --> Json --> Bson

// Object --> JSON 
using System.Web.Extensions;
using System.Web;
using System.Web.Script.Serialization;
JavaScriptSerializer js = new JavaScriptSerializer();
string json = js.Serialize(poco);    // poco is ur class object

//JSON --> BSON
MongoDB.Bson.BsonDocument document = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(json);


回答4:

Going from .NET objects to JSON to BSON, while at the same time avoiding _t/_v encoding, will result in loss of type information for dates and other BSON-specific types.

The solution that I found for avoiding _t/_v while preserving the BSON types was to register a custom value serializer for the driver’s built-in DictionaryInterfaceImplementerSerializer (and optionally the EnumerableInterfaceImplementerSerializer, too). I have tested the solution for “Extended JSON” generation, i.e. JSON with special syntax for the BSON-specific types, but the principle should be the same for direct BSON output.

To do this, first copy the standard ObjectSerializer into your C# project (and rename it to avoid ambiguity): https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Bson/Serialization/Serializers/ObjectSerializer.cs

In addition to the class renaming, the private void SerializeDiscriminatedValue(BsonSerializationContext context, BsonSerializationArgs args, object value, Type actualType) method also needs to be edited. (In my case, I also wanted to avoid _t/_v encoding for IEnumerable<object>, which is reflected below.)

    private void SerializeDiscriminatedValue(BsonSerializationContext context, BsonSerializationArgs args, object value, Type actualType)
    {
        var serializer = BsonSerializer.LookupSerializer(actualType);
        var polymorphicSerializer = serializer as IBsonPolymorphicSerializer;
        // Added line
        var assignableToDictionaryOrEnumerable = typeof(IDictionary<string, object>).IsAssignableFrom(actualType) || typeof(IEnumerable<object>).IsAssignableFrom(actualType);
        if (polymorphicSerializer != null && polymorphicSerializer.IsDiscriminatorCompatibleWithObjectSerializer)
        {
            serializer.Serialize(context, args, value);
        }
        else
        {
            // Edited line
            if (assignableToDictionaryOrEnumerable || (context.IsDynamicType != null && context.IsDynamicType(value.GetType())))
            {
                // We want this code to be executed for types that should be serialized without _t and _v fields
                args.NominalType = actualType;
                serializer.Serialize(context, args, value);
            }
            else
            {
                var bsonWriter = context.Writer;
                var discriminator = _discriminatorConvention.GetDiscriminator(typeof(object), actualType);

                bsonWriter.WriteStartDocument();
                bsonWriter.WriteName(_discriminatorConvention.ElementName);
                BsonValueSerializer.Instance.Serialize(context, discriminator);
                bsonWriter.WriteName("_v");
                serializer.Serialize(context, value);
                bsonWriter.WriteEndDocument();
            }
        }
    }

Assuming that the name of the copied and edited class is DynamicValueSerializer, use the following code to register it as a value serializer at application startup.

BsonSerializer.RegisterSerializer(typeof(Dictionary<string, object>),
    new DictionaryInterfaceImplementerSerializer<Dictionary<string, object>, string, object>(
        DictionaryRepresentation.Document,
        new StringSerializer(),
        new DynamicValueSerializer()));

Please note that this has to be done for each actual type, whose values it should be used for. The serializer registration cannot be done for interfaces. Thus, if SortedDictionary<string, object> or SortedList<string, object> are to be treated in the same way as Dictionary<string, object>, the registration needs to be repeated for those types. (This affects whether dictionaries contained as values in collections of those types will be serialized without _t/_v, not necessarily whether the objects of those types themselves will be serialized in that manner.)

To apply the same serialization principle to List<object> objects, use the following registration code.

BsonSerializer.RegisterSerializer(typeof(List<object>),
    new EnumerableInterfaceImplementerSerializer<List<object>, object>(
        new DynamicValueSerializer()));