OdeToCode IC Logo

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());

.