PhilipMat

JSON Entity Converter

Whether in the context of my friend Curtis’s BORAX proposal, or my own, we will eventually need to serialize our domain objects and ship them to the browser.

Our best solutions is probably some sort of Domain Transfer Object (DTO), but for a second let’s assume we take the quick route and turn our domain objects directly into JSON mush.

Is ASP.NET MVC 3 we can use the JsonResult class, but with it comes a problem: the JavaScriptSerializer, which JsonResult uses, cannot handle circular references (and rightfully so since JSON doesn’t have references).

Let me illustrate using the following simplified domain classes (you can find the full code in this Gist):

public class Entity {  // Layer Supertype
    public int Id { get; set; }
}

public class Company : Entity { 
    public string Name { get; set; }
    public Contact MainContact { get; set; }
}

public class Contact : Entity {
    public string Name { get; set; }
    public Company WorksFor { get; set; }
}

When serializing an object of type Company, the JavaScriptSerializer will choke on Contact.WorksFor since it points back to the Company that the contact is associated with.

Before we talk about how we’ll fix it, let’s talk about the output. What I think I’d like to have when serialize a company to JSON is:

{
    "Id" : 1,
    "Name" : "Moof",
    "MainContact" : {
        "Id" : 100,
        "Name" : "Clarus",
        "WorksFor" : {
            "TypeName" : "Company",
            "Id" : 1
        }
    }
}

In other words, if one of the object we try to serialize has a property of type Entity, we’ll just output its Id and assume the consumer of the JSON will know to request /refdata/{entitytypes}/{id} to resolve it, if needed.

Of course, this will not avoid circular references entirely, but merely provide an example on how to work with the class that will solve our particular problem: JavaScriptConverter.

JavaScriptConverter, of the System.Web.Script.Serialization clan, allows us to inject specific type handlers into JavaScriptSerializer and have it defer to our converter when encountering those types in the serialization process. And it gets better: our converter won’t have to provide actual JSON, it merely has to return a dictionary of name-value pairs and the JavaScriptSerializer will take care of the actual transformation.

Here’s how we could implement the required Serialize method:

public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) {
    var ret = new Dictionary<string, object>();
    string name; object value;
    
    var props = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);
    foreach (PropertyInfo prop in props) {
        name = prop.Name;
        value = prop.GetValue(obj, null);
        if (value != null && typeof(Entity).IsAssignableFrom(prop.PropertyType))
            // real gist code: ret.Add(name, _reducer((Entity) value)); //
            ret.Add(name, new Dictionary<string, object> { 
                { "TypeName", value.GetType().name },
                { "Id", ((Entity) value).Id }
            });
        else
            ret.Add(name, value);
    }
    
    return ret;
}

(Of course the actual implementation is a bit more elegant, I hope)

We can now serialize objects such as these:

var co = new Company { Id = 1, Name = "Moof Inc." };
var clarus = new Contact { Id = 1, Name = "Clarus" };
var mark = new Contact { Id = 2, Name = "Mark" };
co.AddContact(clarus);
co.AddContact(mark);
co.MainContact = clarus;

var converter = new EntityConverter(new[] { typeof(Contact) });
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { converter });
serializer.Serialize(co).Dump(); 

And get the following JSON representation:

{
    "Id": 1, 
    "Name": "Moof Inc.",
    "Contacts": [
        {
            "Id": 1, 
            "Name": "Clarus", 
            "WorksFor": {
                "Id": 1, 
                "TypeName": "Company"
            }
        }, 
        {
            "Id": 2, 
            "Name": "Mark", 
            "WorksFor": {
                "Id": 1, 
                "TypeName": "Company"
            }
        }
    ], 
    "MainContact": {
        "Id": 1, 
        "Name": "Clarus", 
        "WorksFor": {
            "Id": 1, 
            "TypeName": "Company"
        }
    } 
}

The full code - designed to work with the most excellect LINQPad, shows three attempts (four if we include the failed circular reference error) and with them the various ways the EntityConverter can control the JSON output.

If you want to use the code in Visual Studio, add a reference to System.Web.Extensions and import the System.Web.Script.Serialization namespace.