Deserializing JSON into a list of abstract base classes

More adventures in JSON serialization. I’m tellin’ ya, it’s like a Saturday morning cartoon, it’s so exciting. And I’m afraid that’s about as far off topic as I’m going to get with this one because there’ll be a lot of code which tends to push posts into the "Sweet Jayzus, ya think I got that kind of attention span?" territory in my RSS reader. I promise I won’t try to learn ya too much agin in the near future.

The scenario

I’m serializing to the browser a List<Location>. Location is an abstract class with two implementations: RuralLocation and UrbanLocation. The method I’m using to serialize this collection to JSON is based on Scott Guthrie’s post (though updated to use the DataContractJsonSerializer class:

public static string ToJson( this object obj )
{
    var serializer = new DataContractJsonSerializer( obj.GetType( ) );
    var memoryStream = new MemoryStream( );
    serializer.WriteObject( memoryStream, obj );
    string jsonString = Encoding.Default.GetString( memoryStream.ToArray( ) );
    memoryStream.Close( );
    return jsonString;
}

Here’s the code to deserialize:

public static T ToObject( this string jsonString )
{
    var serializer = new DataContractJsonSerializer( typeof ( T ) );
    var memoryStream = new MemoryStream( Encoding.Unicode.GetBytes( jsonString ) );
    var newObject = (T)serializer.ReadObject( memoryStream );
    memoryStream.Close( );
    return newObject;
}

The problem (until this morning) was, I couldn’t use the code to deserialize. On my page, I was creating JSON objects representing RuralLocation objects and UrbanLocation objects with an AJAX call using this same ToJson method. Here’s the MVC action to create an UrbanLocation JSON string:

public void GetUrbanLocationAsJson( string Lot, string Block,
        string Plan, string Address )
{
    var location = new UrbanLocation
    {
        Lot = Lot,
        Block = Block,
        Plan = Plan,
        Address = Address
    };
    Response.ContentType = "application/x-javascript";
    Response.Write( location.ToJson( ) );
}

This returns the following JSON string: {"Lot":"1", "Block":"2", "Plan":"3", "Address":"111 Hoe Down Way"}. And the corresponding method for RuralLocations created something similar.

The issue was that on the client, I had a collection of heterogenous JSON objects with no indication of what type they were. Yes, each one inherited from the same base class but there was no sign of the type when it was serialized. So when I got the JSON string back on the server, I couldn’t make a call like Location[] locations = jsonString.ToObject<Location[]>( ) because it would try to create actual Location objects, not the inherited UrbanLocation and RuralLocation objects.

At the time, my solution was to get any solution working so I deserialized old school: by parsing the string and building the objects by hand. But this morning, I noticed the output of a call to myLocations.ToJson( ). It had the objects the way I expected but there was something else tagging along for the ride:

[
  {"__type":"UrbanLocation:#Trilogy.Gunton.Model",
    "Address":"",
    "Block":"115",
    "Lot":"2",
    "Plan":"2"
  },
  {"__type":"RuralLocation:#Trilogy.Gunton.Model",
    "Lsd":"NW",
    "PrimeMeridian":"1W",
    "Range":"19",
    "Section":"23",
    "Township":"10"
}]

So it seems the JSON serializer *was* including type information, but only when it serialized a list of Location objects, not when it serialized a single RuralLocation or UrbanLocation. So that’s easy, once I get the JSON from the server for a location, I can manually add a __type to it in the Javascript and populate it with the typename before I send it back to the server.

I’ll admit, I actually did consider such nastiness. But then my well-honed Hack Alert started going off as I hope it did for each of you.

What I really wanted was to have the ToJson method specify the type in the resulting string. And to accomplish that, I modified the ToJson method accordingly (changes in bold):

public static string ToJson( this object obj )
{
    return ToJson( obj, obj.GetType( ) );
}

public static string ToJson( this object obj, Type baseClass )
{
    var serializer = new DataContractJsonSerializer( baseClass );
    var memoryStream = new MemoryStream( );
    serializer.WriteObject( memoryStream, obj );
    string jsonString = Encoding.Default.GetString( memoryStream.ToArray( ) );
    memoryStream.Close( );
    return jsonString;
}

And I modified the final line in GetUrbanLocationAsJson to: Response.Write( location.ToJson( typeof( Location ) ) ).

Now, when I retrieve Location objects as JSON, they have the __type included and I can call myLocations.ToObject<Location[]>( ) freely without having to parse a JSON string manually. Hooray!

Boy, this is so much easier than learning Ruby.

Kyle the Unparsed

This entry was posted in Javascript. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://abhijeetmaharana.com Abhijeet

    Good stuff … Thanks!

  • http://blogs.microsoft.co.il/blogs/urig urig

    “Boy, this is so much easier than learning Ruby.”

    Hah! ROTFLMAO! :D

    Great info. Wasn’t that hard to read at all (Give us readers some credit!). I’m off to learn about Scott
    gu’s JSON kung-fu.

    Thanks!