C# Driver - Registering a Custom Serializer for a Generic Type

1,950 views
Skip to first unread message

Daniel Harman

unread,
Aug 16, 2011, 7:49:53 PM8/16/11
to mongodb-user
Following on from some earlier posts about the C# drivers approach to
serialising Dictionarys, I need to write a custom serializer so that
they are stored in a searchable and atomically updateable fashion like
so:

"Participants" : [
{
k : ObjectId("4e3de6255fc9d40fd437a5ac"),
v : ISODate("2011-08-07T01:11:12.410Z")
},
{
k : ObjectId("4e3de6305fc9d40fd437a5ae"),
v : ISODate("2011-08-07T01:11:45.410Z")
}
]

as opposed to the nested array approach which the driver uses if the
key is not a string (and if its a string, its also a problem for
atomicity+searching as it creates a document with fields named as the
key)

"Participants" : [
[
ObjectId("4e3de6255fc9d40fd437a5ac"),
ISODate("2011-08-07T01:11:12.410Z")
],
[
ObjectId("4e3de6305fc9d40fd437a5ae"),
ISODate("2011-08-07T01:11:45.410Z")
],

Anyway, I've written a serializer which is a trivial change to the
default Dictionary<,> serializer, but I'm not sure how to register it
as its a generic as BsonSerializer.RegisterType() doesn't look like it
would work... There is an undocumented method
BsonSerializer.RegisterGenericSerializerDefinition() but no examples
of how to use it in the driver source code.

The serializer I have written is based on deriving a type

CustomDictionary<K,V> : Dictionary<K, V>

just to give it a unique type and not class with the default impl. The
code is as follow (n.b. untested as was planning to test registered
version). Not sure if I need to retrieve the field names when
deserializing as I don't care about them, so ignoring them but might
be an issue if the order of serialization/deserialization is non
deterministic.


/* Based on original driver code from 10gen (see below). Modified by
Daniel Harman.
*
* Copyright 2010-2011 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Bson;

namespace ScampsNet.Repositories.MongoDb.Serializers
{

/// <summary>
/// Represents a serializer for dictionaries.
/// </summary>
public class CustomDictionarySerializer<TKey, TValue> :
BsonBaseSerializer
{
#region constructors
/// <summary>
/// Initializes a new instance of the DictionarySerializer class.
/// </summary>
public CustomDictionarySerializer() {
}
#endregion

#region public methods
/// <summary>
/// Deserializes an object from a BsonReader.
/// </summary>
/// <param name="bsonReader">The BsonReader.</param>
/// <param name="nominalType">The nominal type of the object.</
param>
/// <param name="actualType">The actual type of the object.</param>
/// <param name="options">The serialization options.</param>
/// <returns>An object.</returns>
public override object Deserialize(
BsonReader bsonReader,
Type nominalType,
Type actualType, // ignored
IBsonSerializationOptions options
) {
var bsonType = bsonReader.CurrentBsonType;
if (bsonType == BsonType.Null) {
bsonReader.ReadNull();
return null;
} else if (bsonType == BsonType.Array) {
var dictionary = CreateInstance(nominalType);
bsonReader.ReadStartArray();
var keyDiscriminatorConvention =
BsonDefaultSerializer.LookupDiscriminatorConvention(typeof(TKey));
var valueDiscriminatorConvention =
BsonDefaultSerializer.LookupDiscriminatorConvention(typeof(TValue));
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) {
bsonReader.ReadStartDocument();
bsonReader.ReadBsonType();
var keyType =
keyDiscriminatorConvention.GetActualType(bsonReader, typeof(TKey));
var keySerializer = BsonSerializer.LookupSerializer(keyType);
var key = (TKey) keySerializer.Deserialize(bsonReader,
typeof(TKey), keyType, null);
bsonReader.ReadBsonType();
var valueType =
valueDiscriminatorConvention.GetActualType(bsonReader,
typeof(TValue));
var valueSerializer = BsonSerializer.LookupSerializer(valueType);
var value = (TValue) valueSerializer.Deserialize(bsonReader,
typeof(TValue), valueType, null);
bsonReader.ReadEndDocument();
dictionary.Add(key, value);
}
bsonReader.ReadEndArray();
return dictionary;
} else {
var message = string.Format("Can't deserialize a {0} from BsonType
{1}.", nominalType.FullName, bsonType);
throw new FileFormatException(message);
}
}

/// <summary>
/// Serializes an object to a BsonWriter.
/// </summary>
/// <param name="bsonWriter">The BsonWriter.</param>
/// <param name="nominalType">The nominal type.</param>
/// <param name="value">The object.</param>
/// <param name="options">The serialization options.</param>
public override void Serialize(
BsonWriter bsonWriter,
Type nominalType,
object value,
IBsonSerializationOptions options
) {
if (value == null) {
bsonWriter.WriteNull();
} else {
var dictionary = (IDictionary<TKey, TValue>) value;

bsonWriter.WriteStartArray();
foreach (KeyValuePair<TKey, TValue> entry in dictionary) {
bsonWriter.WriteStartDocument();
bsonWriter.WriteName("k");
BsonSerializer.Serialize(bsonWriter, typeof(TKey), entry.Key);
bsonWriter.WriteName("v");
BsonSerializer.Serialize(bsonWriter, typeof(TValue),
entry.Value);
bsonWriter.WriteEndDocument();
}
bsonWriter.WriteEndArray();
}
}
#endregion

#region private methods
private IDictionary<TKey, TValue> CreateInstance(
Type nominalType
) {
if (nominalType == typeof(Dictionary<TKey, TValue>)) {
return new Dictionary<TKey, TValue>();
} else if (nominalType == typeof(IDictionary<TKey, TValue>)) {
return new Dictionary<TKey, TValue>();
} else if (nominalType == typeof(SortedDictionary<TKey, TValue>)) {
return new SortedDictionary<TKey, TValue>();
} else if (nominalType == typeof(SortedList<TKey, TValue>)) {
return new SortedList<TKey, TValue>();
} else {
var message = string.Format("Invalid nominalType {0} for
DictionarySerializer<{1}, {2}>.", nominalType.FullName,
typeof(TKey).FullName, typeof(TValue).FullName);
throw new BsonSerializationException(message);
}
}
#endregion
}

}

craiggwilson

unread,
Aug 18, 2011, 8:17:44 AM8/18/11
to mongod...@googlegroups.com
Looks like you can do one of two things.  I know #1 will work.  I'd try #2 first just because it is easier...
 
1) Create a custom IBsonSerializerProvider and register it.  This is basically a hook into the internals that will call out to you and say, "Hey, do you have something that can handle this type?".
 
2) Use the BsonSerializer.RegisterGenericSerializerDefinition(typeof(CustomDictionary<,>), typeof(CustomDictionarySerializer<,>));
 
Hope that works...

Daniel Harman

unread,
Aug 20, 2011, 6:31:28 PM8/20/11
to mongodb-user

Thanks Craig,

I got it all working.

Dan
Reply all
Reply to author
Forward
0 new messages