Custom Serialization with JSON.NET, WebAPI, and BsonDocument

Monday, September 30, 2013

JSON.NET has a simple and easy extensibility model, which is fortunate because I recently ran into problems serializing a collection of BsonDocument.

The first problem with serializing a BsonDocument is how each document exposes public conversion properties like AsInt32, AsBoolean, and AsDateTime. Trying to serialize all public properties  is guaranteed to throw an exception on at least one of these conversion properties.

Fortunately, the MongoDB C# driver includes a ToJson extension method which doesn’t try to serialize the conversion properties. But, ToJson can also create problems because ToJson doesn’t produce conforming JSON by default. For example, ObjectId is represented in the output as ObjectId("… id …"), which causes JSON.parse to fail in a browser.

The solution is to provide a custom JSON.NET converter which uses the ToJson method with some custom settings. In the following code, IMongoDbCursor is a custom abstraction I have to wrap server cursors in Mongo, and is essentially an IEnumerable<BsonDocument>.

public class MongoCursorJsonConverter : JsonConverter
{
    public override void WriteJson(
        JsonWriter writer, object value, JsonSerializer serializer)
    {
        var cursor = (IMongoDbCursor) value;
        var settings = new JsonWriterSettings
            {
                OutputMode = JsonOutputMode.Strict
            };

        writer.WriteStartArray();

        foreach (BsonDocument document in cursor)
        {
            writer.WriteRawValue(document.ToJson(settings));            
        }

        writer.WriteEndArray();
    }

    public override object ReadJson(
        JsonReader reader, Type objectType, 
        object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanConvert(Type objectType)
    {
        var types = new[] {typeof (MongoDbCursor)};
        return types.Any(t => t == objectType);
    }
}

The whole key is the foreach statement inside of WriteJson, which uses WriteRawValue to put strictly conforming JSON in place. Another option is to treat a BsonDocument as an IDictionary, which breaks down complex values into primitives.

writer.WriteStartArray();

foreach (BsonDocument document in cursor)
{   
    serializer.Serialize(writer, document.ToDictionary());
}

writer.WriteEndArray();

The last piece is to plugin the converter during WebApi configuration.

var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
jsonFormatter.SerializerSettings.Converters.Add(new MongoCursorJsonConverter());

.


Comments
gravatar Alexander Tuesday, October 1, 2013
Your CanConvert() makes no sense, does it? I mean it's inefficient. ruturn typeof(MongoDbCursor) == objectType; or better return objectType.IsAssignableFrom(typeof(MongoDbCursor));
gravatar Scott Allen Tuesday, October 1, 2013
@Alex: yes, true. It was going to contain some additional types, but IsAssignableFrom would be better after all.
gravatar Craig Wilson Tuesday, October 1, 2013
This is a fine way to handle this. There is an overload of ToJson that accepts JsonWriterSettings. Changing the mode to Strict will alter how ObjectIds (and certain other types) are serialized.
gravatar Scott Allen Tuesday, October 1, 2013
@Craig: Yes thank, that's just what happens inside of the first WriteJson method.
gravatar Craig Wilson Tuesday, October 1, 2013
Yup, I see that now. If I remembered what I read 3 lines before... I'm getting old :(
gravatar Filip Friday, October 4, 2013
It's worth mentioning that WebApiContrib has a BSON formatter which can be pulled from Nuget: - https://github.com/WebApiContrib/WebApiContrib.Formatting.Bson - http://www.nuget.org/packages/WebApiContrib.Formatting.Bson/
gravatar devtools.korzh Sunday, October 6, 2013
Nice, thank you for the post!
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!