Deserializing a JSon to a list of abstract types (or interfaces) with Newtonsoft Json.NET Converter

Let’s consider this situation: we have a base abstract class Animal and several subclasses, each of them dedicated for a specific animal kind. Let’s consider that we have a collection of animals and we want to serialize/deserialize it. Here are the classes we are using:

public abstract class Animal
{
    public string Name { get; set; }
    public string Color { get; set; }
    public abstract Constants.AnimalType Type { get; }
    public abstract string Noise { get; }
}

public class Cat : Animal
{
    public override Constants.AnimalType Type
    {
        get { return Constants.AnimalType.Cat; }
    }
 
    public override string Noise
    {
        get { return "miao"; }
    }
}

public class Dog : Animal
{
    public override Constants.AnimalType Type
    {
        get { return Constants.AnimalType.Dog; }
    }
 
    public override string Noise
    {
        get { return "bau"; }
    }
}

public class Pig : Animal
{
    public override Constants.AnimalType Type
    {
        get { return Constants.AnimalType.Pig; }
    }
 
    public override string Noise
    {
        get { return "oink"; }
    }
}

Now let’s see what happens if we try to serialize/deserialize our animal’s collection as a normal object:

class Program
{
    static void Main(string[] args)
    {
        var animals = new List<Animal>
        {
            new Cat
            {
                Name = "felix",
                Color = "black"
            },
            new Dog
            {
                Name = "bobby",
                Color = "brown"
            },
            new Pig
            {
                Name = "peppa",
                Color = "pink"
            }
        };
 
        var json = JsonConvert.SerializeObject(animals);
 
        animals = JsonConvert.DeserializeObject<List<Animal>>(json);
    }
}

What happens is that we have an error during the deserialization. The error message is: “Could not create an instance of type Animal. Type is an interface or abstract class and cannot be instantiated.” This is because while JsonConvert is deserializing this Json:

[{
          "Type" : 0,
          "Noise" : "miao",
          "Name" : "felix",
          "Color" : "black"
     }, {
          "Type" : 1,
          "Noise" : "bau",
          "Name" : "bobby",
          "Color" : "brown"
     }, {
          "Type" : 3,
          "Noise" : "oink",
          "Name" : "peppa",
          "Color" : "pink"
     }
]

to a list of Animal, is not able to detect the correct subclass to use for each element, so it just try to create instances of Animal. Since Animal is an abstract class hence the error. How to solve it? We need to tell JsonConvert how to manage our subclasses.

First of all we need to use the JsonCreationConverter class, that will help us to create our converter. This class is bundled in Json.NET from the version 7.0, so if we are using an earlier version we need to manually write it. Here is the version I use:

public abstract class JsonCreationConverter<T> : JsonConverter
{
    protected abstract T Create(Type objectType, JObject jObject);
 
    public override bool CanConvert(Type objectType)
    {
        return typeof(T) == objectType;
    }
 
    public override object ReadJson(JsonReader reader, Type objectType,
        object existingValue, JsonSerializer serializer)
    {
        try
        {
            var jObject = JObject.Load(reader);
            var target = Create(objectType, jObject);
            serializer.Populate(jObject.CreateReader(), target);
            return target;
        }
        catch (JsonReaderException)
        {
            return null;
        }
    }
 
    public override void WriteJson(JsonWriter writer, object value,
        JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Then we can use the JsonCreationConverter class to create our specific converter for our animals. Basically we must specify which subclass to use based on the value of some common field, as the AnimalType in our case. Here is how to write it:

public class AnimalConverter : JsonCreationConverter<Animal>
{
    protected override Animal Create(Type objectType, JObject jObject)
    {
        switch ((Constants.AnimalType)jObject["Type"].Value<int>())
        {
            case Constants.AnimalType.Cat:
                return new Cat();
            case Constants.AnimalType.Dog:
                return new Dog();
            case Constants.AnimalType.Pig:
                return new Pig();
        }
        return null;
    }
}

Finally we just need to pass our custom converter to the JsonConvert.DeserializeObject() method in this way:

animals = JsonConvert.DeserializeObject<List<Animal>>(json, new AnimalConverter());
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s