[CSHARP-1578] Subclass of BsonDocument is not getting serialized Created: 25/Feb/16  Updated: 05/Apr/19  Resolved: 04/Apr/16

Status: Closed
Project: C# Driver
Component/s: Serialization
Affects Version/s: 2.2.3
Fix Version/s: None

Type: Task Priority: Minor - P4
Reporter: Rajesh Reddy Assignee: Robert Stam
Resolution: Done Votes: 0
Labels: question
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Windows 7 64 bit with C# driver 2.2.3 and MongoDB 3.2.3


Attachments: PNG File mongodb 3.2.3 serialization error message.png     PNG File mongodb 3.2.3 serialization error.png     PNG File mongodb 3.2.3 serialization valid.png    

 Description   

I have created a subclass from BsonDocument as below

public class MyClassDocument : BsonDocument
{

}

But, MyClassDocument class is not getting serialized while making calls to any of the API methods,

Error Message:
MongoDB.Driver.MongoCommandException: Command insert failed: error parsing element 0 of field documents :: caused by :: wrong type for '0' field, expected object, found 0: [ {}, {} ]



 Comments   
Comment by Robert Stam [ 29/Feb/16 ]

We would actually recommend that you not subclass BsonDocument.

Can you take some other approach?

Comment by Rajesh Reddy [ 27/Feb/16 ]

Thanks for the update @Robert Stam

Comment by Robert Stam [ 25/Feb/16 ]

I am able to reproduce this. We weren't really expecting anyone to subclass BsonDocument, and as you have found out, if you do that, serialization of your custom subclass does not work out of the box.

I used this class to reproduce this:

public class MyBsonDocument : BsonDocument
{
    public MyBsonDocument()
    {
    }
 
    public MyBsonDocument(IEnumerable<BsonElement> elements)
        : base(elements)
    {
    }
}

To understand how MyBsonDocument was being serialized I added the following line of code at the beginning of my test program:

var myBsonDocumentSerializer = BsonSerializer.LookupSerializer<MyBsonDocument>();

And using the debugger I found that the type of the serializer was an instance of an EnumerableInterfaceImplementerSerializer<MyBsonDocument, BsonElement>. The driver chose this serializer because MyBsonDocument implements IEnumerable<BsonElement> (it inherits it from BsonDocument), and we assume that the usual serialization of any class that implements IEnumerable<T> is as an array of T. So that's why you see your MyBsonDocument value serialized as an array.

In this case T is BsonElement, and the driver then needs a serializer for BsonElement. We can see which one it chooses with this line of code:

var bsonElementSerializer = BsonSerializer.LookupSerializer<BsonElement>();

And find that it has chosen a BsonClassMapSerializer. Unfortunately, BsonClassMapSerializer does not work with BsonElement, so the result is that the BsonElement get serialized as "{ }".

If you really want to subclass BsonDocument what you will need to do is wire things up so that your MyBsonDocument values get serialized correctly. You can do that with the following adapter serializer which delegates most of the work to the existing BsonDocumentSerializer:

public class MyBsonDocumentSerializer : SerializerBase<MyBsonDocument>
{
    public override MyBsonDocument Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        var elements = BsonDocumentSerializer.Instance.Deserialize(context, args);
        return new MyBsonDocument(elements);
    }
 
    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, MyBsonDocument value)
    {
        args = new BsonSerializationArgs { NominalType = typeof(BsonDocument), SerializeAsNominalType = true };
        BsonDocumentSerializer.Instance.Serialize(context, args, value);
    }
}

And then you have to tell the driver to use this serializer for instances of MyBsonDocument. The easiest way is to use the [BsonSerializer] attribute on the MyBsonDocument class:

[BsonSerializer(typeof(MyBsonDocumentSerializer))]
public class MyBsonDocument : BsonDocument
{
    ...
}

Now the driver has been configured to correctly serialize instances of MyBsonDocument. The test I used was:

var document = new MyBsonDocument
{
    { "m", new MyBsonDocument { { "a", 1 }, {  "b", 2 } } }
};
var json = document.ToJson();
 
var rehydrated = BsonSerializer.Deserialize<MyBsonDocument>(json);

Which produced the following value for the json variable:

{ "m" : { "a" : 1, "b" : 2 } }

One final note: since instances of MyBsonDocument are now serialized exactly the same as a BsonDocument, there is no way during deserialization to tell the difference between a BsonDocument and a MyBsonDocument. So in the code sample above, the rehydrated variable contains an instance of MyBsonDocument, but the value of the "m" element is a regular BsonDocument.

Generated at Wed Feb 07 21:40:02 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.