JSON serialization of array with polymorphic objects JSON serialization of array with polymorphic objects json json

JSON serialization of array with polymorphic objects


Json.NET has a neat solution for this. There is a setting that intelligently adds type information - declare it like this:

new JsonSerializer { TypeNameHandling = TypeNameHandling.Auto };

This will determine whether type embedding is required and add it where necessary. Lets say I had the following classes:

public class Message{    public object Body { get; set; }}public class Person{    public string Name { get; set; }}public class Manager : Person{}public class Department{    private List<Person> _employees = new List<Person>();    public List<Person> Employees { get { return _employees; } }}

Notice the Message Body is of type object, and that Manager subclasses Person. If I serialize a Message with a Department Body that has a single Manager I get this:

{    "Body":    {        "$type":"Department, MyAssembly",        "Employees":[            {                "$type":"Manager, MyAssembly",                "Name":"Tim"            }]    }}

Notice how it's added the $type property to describe the Department and Manager types. If I now add a Person to the Employees list and change the Message Body to be of type Department like this:

public class Message{    public Department Body { get; set; }}

then the Body type annotation is no longer needed and the new Person is not annotated - absence of annotation assumes the element instance is of the declared array type. The serialized format becomes:

{    "Body":    {        "Employees":[            {                "$type":"Manager, MyAssembly",                "Name":"Tim"            },            {                "Name":"James"            }]    }}

This is an efficient approach - type annotation is only added where required. While this is .NET specific, the approach is simple enough to handle that deserializers/message types on other platforms should be fairly easily extended to handle this.

I'd be reticent about using this in a public API though, as it is non-standard. In that case you'd want to avoid polymorphism, and make versioning and type information very explicit properties in the message.


Probably the closest that I've seen is to use the JavaScriptSerializer and pass in a JavaScriptTypeResolver to the constructor. It doesn't produce JSON formatted exactly as you have it in your question, but it does have a _type field that describes the type of the object that's being serialized. It can get a little ugly, but maybe it will do the trick for you.

Here's my sample code:

public abstract class ProductBase{    public String Name { get; set; }    public String Color { get; set; }}public class Drink : ProductBase{}public class Product : ProductBase{}class Program{    static void Main(string[] args)    {        List<ProductBase> products = new List<ProductBase>()        {            new Product() { Name="blah", Color="Red"},            new Product(){ Name="hoo", Color="Blue"},            new Product(){Name="rah", Color="Green"},            new Drink() {Name="Pepsi", Color="Brown"}        };        JavaScriptSerializer ser = new JavaScriptSerializer(new SimpleTypeResolver());        Console.WriteLine(ser.Serialize(products));        }}

And the result looks like this:

[  {"__type":"TestJSON1.Product, TestJSON1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","Name":"blah","Color":"Red"},  {"__type":"TestJSON1.Product, TestJSON1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","Name":"hoo","Color":"Blue"},  {"__type":"TestJSON1.Product, TestJSON1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","Name":"rah","Color":"Green"},  {"__type":"TestJSON1.Drink, TestJSON1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","Name":"Pepsi","Color":"Brown"}]

I'm using the SimpleTypeConverter, which is part of the framework by default. You can create your own to shorten what's returned by __type.

EDIT: If I create my own JavaScriptTypeResolver to shorten the type name returned, I can produce something like this:

[  {"__type":"TestJSON1.Product","Name":"blah","Color":"Red"},  {"__type":"TestJSON1.Product","Name":"hoo","Color":"Blue"},  {"__type":"TestJSON1.Product","Name":"rah","Color":"Green"},  {"__type":"TestJSON1.Drink","Name":"Pepsi","Color":"Brown"}]

Using this converter class:

public class MyTypeResolver : JavaScriptTypeResolver{    public override Type ResolveType(string id)    {        return Type.GetType(id);    }    public override string ResolveTypeId(Type type)    {        if (type == null)        {            throw new ArgumentNullException("type");        }        return type.FullName;    }}

And just passing it into my JavaScriptSerializer constructor (instead of the SimpleTypeConverter).

I hope this helps!


1) You can use a Dictionary<string,object> to do the job,...

[{"Cat":{"Name":"Pinky"}},{"Cat":{"Name":"Winky"}},{"Dog":{"Name":"Max"}}]

public class Cat {    public string Name { get; set; }}public class Dog {    public string Name { get; set; }}    internal static void Main()    {        List<object> animals = new List<object>();        animals.Add(new Cat() { Name = "Pinky" });        animals.Add(new Cat() { Name = "Winky" });        animals.Add(new Dog() { Name = "Max" });        // Convert every item in the list into a dictionary        for (int i = 0; i < animals.Count; i++)        {            var animal = new Dictionary<string, object>();            animal.Add(animals[i].GetType().Name, animals[i]);            animals[i] = animal;        }        var serializer = new JavaScriptSerializer();        var json = serializer.Serialize(animals.ToArray());        animals = (List<object>)serializer.Deserialize(json, animals.GetType());        // convert every item in the dictionary back into a list<object> item        for (int i = 0; i < animals.Count; i++)        {            var animal = (Dictionary<string, object>)animals[i];            animal = (Dictionary<string, object>)animal.Values.First();            animals[i] = animal.Values.First();        }    }

2) Or using the JavaScriptConverter it is possible to handle the serialization for a type.

[{"cat":{"Omnivore":true}},{"aardvark":{"Insectivore":false}},{"aardvark":{"Insectivore":true}}]

abstract class AnimalBase { }class Aardvark : AnimalBase{    public bool Insectivore { get; set; }}class Dog : AnimalBase{    public bool Omnivore { get; set; }}class AnimalsConverter : JavaScriptConverter{    private IDictionary<string, Type> map;    public AnimalsConverter(IDictionary<string, Type> map) { this.map = map; }    public override IEnumerable<Type> SupportedTypes    {        get { return new Type[]{typeof(AnimalBase)}; }    }    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)    {        var result = new Dictionary<string, object>();        var type = obj.GetType();        var name = from x in this.map where x.Value == type select x.Key;        if (name.Count<string>() == 0)            return null;        var value = new Dictionary<string, object>();        foreach (var prop in type.GetProperties())        {            if(!prop.CanRead) continue;            value.Add(prop.Name, prop.GetValue(obj, null));        }        result.Add(name.First<string>(), value);        return result;    }    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)    {        var keys = from x in this.map.Keys where dictionary.ContainsKey(x) select x;        if (keys.Count<string>() <= 0) return null;        var key = keys.First<string>();        var poly = this.map[key];        var animal = (AnimalBase)Activator.CreateInstance(poly);        var values = (Dictionary<string, object>)dictionary[key];        foreach (var prop in poly.GetProperties())        {            if(!prop.CanWrite) continue;            var value = serializer.ConvertToType(values[prop.Name], prop.PropertyType);            prop.SetValue(animal, value, null);        }        return animal;    }}class Program{    static void Main(string[] args)    {        var animals = new List<AnimalBase>();        animals.Add(new Dog() { Omnivore = true });        animals.Add(new Aardvark() { Insectivore = false });        animals.Add(new Aardvark() { Insectivore = true });        var convertMap = new Dictionary<string, Type>();        convertMap.Add("cat", typeof(Dog));        convertMap.Add("aardvark", typeof(Aardvark));        var converter = new AnimalsConverter(convertMap);        var serializer = new JavaScriptSerializer();        serializer.RegisterConverters(new JavaScriptConverter[] {converter});        var json = serializer.Serialize(animals.ToArray());        animals.Clear();        animals.AddRange((AnimalBase[])serializer.Deserialize(json, typeof(AnimalBase[])));    }}