Problem with C# driver deserializing fields that are Dictionary<string,object> type

4,712 views
Skip to first unread message

DaveC

unread,
Mar 23, 2012, 10:00:56 PM3/23/12
to mongod...@googlegroups.com
I've got some C# classes that have fields that are of type Dictionary<string,object>.  An example of the data for one of these fields can be seen below, snipped from a larger BSON document:

 "dictionaryOfItems" : {
                "_t" : "System.Collections.Generic.Dictionary`2[System.String,System.Object]",
                "_v" : {
                        "somedata1" : "whatever",
                        "somedata2" : 1234,
                        "somedate3" : NumberLong(7890)
                }
        }

However, I don't seem to be able to deserialize this field into a Dictionary<string,object>.  I've got a custom serializer that works on any other type of field, but when deserializing a Dictionary<string,object>, I get this error:

A document being deserialized to System.Object must be empty.
   at MongoDB.Bson.Serialization.Serializers.ObjectSerializer.Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options) in C:\work\10gen\mongodb\mongo-csharp-driver\Bson\Serialization\Serializers\ObjectSerializer.cs:line 128
   at MongoDB.Bson.Serialization.Serializers.DictionarySerializer`2.Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options) in C:\work\10gen\mongodb\mongo-csharp-driver\Bson\Serialization\Serializers\DictionaryGenericSerializer.cs:line 77
   at MongoDB.Bson.Serialization.Serializers.BsonBaseSerializer.Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options) in C:\work\10gen\mongodb\mongo-csharp-driver\Bson\Serialization\Serializers\BsonBaseSerializer.cs:line 53

It's attempting to deserialize the value fields and using the ObjectSerializer which seems to throw this error if the value is anything more than an empty document.  Is there any way to get it to determine the type of each value in the dictionary?  The DictionarySerializer<string,object> doesn't put any sort of discriminator into the value field.  If not, is there another way I could be serializing / deserializing that would be supported?

Robert Stam

unread,
Mar 24, 2012, 11:52:17 AM3/24/12
to mongod...@googlegroups.com
Are you saying that you are trying to deserialize just part of your larger BSON document?

It would be helpful to have some code to look at. Not sure I have enough to go on here to try and reproduce.

--
You received this message because you are subscribed to the Google Groups "mongodb-user" group.
To view this discussion on the web visit https://groups.google.com/d/msg/mongodb-user/-/XfMV7CC_e4gJ.
To post to this group, send email to mongod...@googlegroups.com.
To unsubscribe from this group, send email to mongodb-user...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/mongodb-user?hl=en.

DaveC

unread,
Mar 24, 2012, 12:22:56 PM3/24/12
to mongod...@googlegroups.com
I'm trying to deserialize the whole document, but get an error only on properties that are Dictionary<string,object>. I didn't post code because I'm doing pretty standard stuff within my custom serializer, but here is where the error occurs within my serializer's Deserialize method:

var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(typeof(object));
var actualType = discriminatorConvention.GetActualType(bsonReader, typeof(object));
var serializer = BsonSerializer.LookupSerializer(actualType);
object propVal = serializer.Deserialize(bsonReader, actualType, options);

The actualType variable returns as Dictionary<string,object>, so the discriminator is working.

The serializer variable is an instance of DictionarySerializer<string,object> as it should be, but it doesn't seem to be able to deserialize the property, or really, the values within the dictionary.

If you need more, I can post the full serializer.

On Saturday, March 24, 2012 11:52:17 AM UTC-4, Robert Stam wrote:
Are you saying that you are trying to deserialize just part of your larger BSON document?

It would be helpful to have some code to look at. Not sure I have enough to go on here to try and reproduce.

On Fri, Mar 23, 2012 at 10:00 PM, DaveC wrote:
I've got some C# classes that have fields that are of type Dictionary<string,object>.  An example of the data for one of these fields can be seen below, snipped from a larger BSON document:

 "dictionaryOfItems" : {
                "_t" : "System.Collections.Generic.​Dictionary`2[System.String,​System.Object]",
                "_v" : {
                        "somedata1" : "whatever",
                        "somedata2" : 1234,
                        "somedate3" : NumberLong(7890)
                }
        }

However, I don't seem to be able to deserialize this field into a Dictionary<string,object>.  I've got a custom serializer that works on any other type of field, but when deserializing a Dictionary<string,object>, I get this error:

A document being deserialized to System.Object must be empty.
   at MongoDB.Bson.Serialization.​Serializers.ObjectSerializer.​Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options) in C:\work\10gen\mongodb\mongo-​csharp-driver\Bson\​Serialization\Serializers\​ObjectSerializer.cs:line 128
   at MongoDB.Bson.Serialization.​Serializers.​DictionarySerializer`2.​Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options) in C:\work\10gen\mongodb\mongo-​csharp-driver\Bson\​Serialization\Serializers\​DictionaryGenericSerializer.​cs:line 77
   at MongoDB.Bson.Serialization.​Serializers.​BsonBaseSerializer.​Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options) in C:\work\10gen\mongodb\mongo-​csharp-driver\Bson\​Serialization\Serializers\​BsonBaseSerializer.cs:line 53

It's attempting to deserialize the value fields and using the ObjectSerializer which seems to throw this error if the value is anything more than an empty document.  Is there any way to get it to determine the type of each value in the dictionary?  The DictionarySerializer<string,​object> doesn't put any sort of discriminator into the value field.  If not, is there another way I could be serializing / deserializing that would be supported?

--
You received this message because you are subscribed to the Google Groups "mongodb-user" group.
To view this discussion on the web visit https://groups.google.com/d/​msg/mongodb-user/-/XfMV7CC_​e4gJ.
To post to this group, send email to mongod...@googlegroups.com.
To unsubscribe from this group, send email to mongodb-user+unsubscribe@​googlegroups.com.

Robert Stam

unread,
Mar 24, 2012, 12:37:00 PM3/24/12
to mongod...@googlegroups.com
If you can post some code that I can run on my machine that would be great. That would make it easy for me to reproduce.

To view this discussion on the web visit https://groups.google.com/d/msg/mongodb-user/-/SdVg3-xxrA8J.

To post to this group, send email to mongod...@googlegroups.com.
To unsubscribe from this group, send email to mongodb-user...@googlegroups.com.

DaveC

unread,
Mar 24, 2012, 5:39:58 PM3/24/12
to mongod...@googlegroups.com
Here is a self-contained sample.  It works as is, but if you uncomment the dictionary field as specified starting at line 28, you'll get the exception.



On Saturday, March 24, 2012 12:37:00 PM UTC-4, Robert Stam wrote:
If you can post some code that I can run on my machine that would be great. That would make it easy for me to reproduce.

Robert Stam

unread,
Mar 25, 2012, 10:48:03 AM3/25/12
to mongod...@googlegroups.com
I see the problem (it's in your custom serializer), but I don't have a solution yet. But I wanted to share what I've found so far. When you run the program with the lines starting at line 28 uncommented, this is the document you get:

> db.MyDyn.find().toArray()
[
        {
                "_id" : ObjectId("4f6f2ab3e447ad05680e330d"),
                "A" : "aaa",
                "B" : "bbb",
                "nums" : [
                        9,
                        8,
                        7,
                        6,
                        5,
                        4,
                        3,
                        2,
                        1
                ],
                "someId" : BinData(4,"B8Vz0prnRhaDSbqGbMlT+Q=="),
                "complex" : {
                        "_t" : "MyDyn",
                        "_id" : null,
                        "A" : "ccc",
                        "B" : "ddd",
                        "foo" : "bar",
                        "l" : 200
                },
                "dict" : {
                        "_t" : "System.Collections.Generic.Dictionary`2[System.String,System.Object]",
                        "_v" : {
                                "someVal1" : "abcd",
                                "someVal2" : 1234,
                                "someVal3" : NumberLong(70000)
                        }
                }
        }
]
>

The issue during deserialization is that you are calling the Dictionary<string, object> deserializer when the reader is positioned at the "dict" name element. Based on the type of the keys (string), the values (object) and the position of the reader, this looks to the Dictionary serializer like a Dictionary of two entries, one called "_t" and one called "_v" (when what you thought you were reading was a Dictionary of three entries called "someVal1", "someVal2" and "someVal3").

At this point it reads the "_t" entry correctly because it can figure out that the value is a string, but when it gets to the "_v" value it doesn't know what to do because there is no discriminator to tell it the type of "_v" (so it therefore assumes it must be an object but if the actual type really were object then the document should be empty because object has no fields).

Not that you should add a discriminator, the fix is to get things synchronized so that when DictionarySerializer<string, object>.Deserialize is called the reader is positioned at the "_v" element and not at the "dict" element. But I haven't read your code in enough detail to figure out the fix. Another option is to not try to figure out the discriminator stuff yourself and just call the ObjectDeserializer, since it already knows how to deal with "_t" discriminators.

Maybe given this additional information you see the fix?

To view this discussion on the web visit https://groups.google.com/d/msg/mongodb-user/-/SLzfVRMZOH4J.

To post to this group, send email to mongod...@googlegroups.com.
To unsubscribe from this group, send email to mongodb-user...@googlegroups.com.

DaveC

unread,
Mar 25, 2012, 5:20:54 PM3/25/12
to mongod...@googlegroups.com
That helps.  

On line 255, I was trying to determine if it was a dictionary so I could specify dictionary options, but I've removed that, and the dict field gets serialized by the DictionarySerializer<string,object> with the null passed as the options, and it still puts the discriminator in there...the serialized data looks exactly the same.

I tried using the ObjectSerializer like you suggested, but that doesn't work at all either:

System.FormatException: Index (zero based) must be greater than or equal to zero and less than the size of the argument list.
   at System.Text.StringBuilder.AppendFormat(IFormatProvider provider, String format, Object[] args)
   at System.String.Format(IFormatProvider provider, String format, Object[] args)
   at System.String.Format(String format, Object arg0)
   at MongoDB.Bson.Serialization.Serializers.ObjectSerializer.Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options) in C:\work\10gen\mongodb\mongo-csharp-driver\Bson\Serialization\Serializers\ObjectSerializer.cs:line 66

The _t and _v fields are because the DictionarySerializer is serializing this field as a document, and this seems to be the default behavior. The only way I can really get this to work is to do this to advance it through the _t field and have the DictionarySerializer.Deserializer called when I should be to the value of the _v field:

bsonReader.ReadStartDocument();
bsonReader.ReadName(); // reads name _t
bsonReader.ReadString(); // reads value of _t
bsonReader.ReadName(); // reads name of _v
object propVal = serializer.Deserialize(bsonReader, actualType, options); // reads value of _v
bsonReader.ReadEndDocument();
BinderHelper.SetMemberValue(value, propName, propVal);

That seems like a hack to me, but it works.

Here is the updated code that works: http://pastebin.com/9kY8jdrX

I'm advancing through the reader starting on line 147. I don't really understand why I have to do this for a dictionary but don't have to do this for any other types.
Reply all
Reply to author
Forward
0 new messages