Polymorphic serialization using Json.NET in HttpContent

Imagine a quite common client-server application where the server exposes REST methods and the client communicates using HTTP requests. Requests and responses are serialized in JSON format. There might be a problem if you try to send and receive a DTO that contains a collection of interfaces or abstract classes. I will talk about the usage of Web API and Newtonsoft Json.NET as its underlying serialization library.

For example we have the following interface and some implementations of it:

interface IVehicle
{
    string Name { get; }
}
 
class Motorbike : IVehicle
{
    public string Name { get; set; }
    public int MaxSpeed { get; set; }
}
 
class Bus : IVehicle
{
    public string Name { get; set; }
    public string Manufacturer { get; set; }
    public int MaxPassengers { get; set; }
}

The DTO looks like

class AvailableVehiclesDto
{
    public List<IVehicle> Vehicles { get; set; }
}

The ASP.NET server sends a collection of vehicles to the client:

[HttpGet]
public HttpResponseMessage GetAvailableVehicles()
{
    var dto = new AvailableVehiclesDto
    {
        Vehicles = new List<IVehicle>
        {
            new Motorbike
            {
                Name = "Ural",
                MaxSpeed = 160
            },
            new Motorbike
            {
                Name = "Harley-Davidson Hummer",
                MaxSpeed = 80
            },
            new Bus
            {
                Name = "Ikarus 280",
                Manufacturer = "Ikarus",
                MaxPassengers = 180
            }
        }
    };
 
    return Request.CreateResponse(HttpStatusCode.OK, dto);
}

On the client side we try to get and deserialize the response:

var response = await _serviceClient.GetAsync(
    "http://localhost:9001/Resources/api/Vehicles/GetAvailableVehicles");

response.EnsureSuccessStatusCode();

var dto = await response.Content.ReadAsAsync<AvailableVehiclesDto>();

But the client will throw an exception JsonSerializationException during deserialization of the collection with the message:

Additional information: Could not create an instance of type IVehicle. Type is an interface or abstract class and cannot be instantiated.

The problem is that client doesn't know what was the initial type of each vehicle before serialization. The JSON of the collection above looks like:

{
    "Vehicles" : [
        {
            "Name" : "Ural",
            "MaxSpeed" : 160
        },
        {
            "Name" : "Harley-Davidson Hummer",
            "MaxSpeed" : 80
        },
        {
            "Name" : "Ikarus 280",
            "Manufacturer" : "Ikarus",
            "MaxPassengers" : 180
        }
    ]
}

This JSON doesn't contain any information about the type of each collection object. In order to solve the issue, JSON formatters should be additionally configured on the client and server sides. The server side:

var config = new HttpConfiguration();
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling =
    TypeNameHandling.Auto;

return Request.CreateResponse(HttpStatusCode.OK, dto, config);

The client-side:

var response = await _serviceClient.GetAsync(
    "http://localhost:9001/Resources/api/Vehicles/GetAvailableVehicles");

response.EnsureSuccessStatusCode();

var formatter = new JsonMediaTypeFormatter
{
    SerializerSettings = { TypeNameHandling = TypeNameHandling.Auto }
};

var dto = await response.Content.ReadAsAsync<AvailableVehiclesDto>(
    new List<MediaTypeFormatter> { formatter });

After these changes the JSON looks like:

{
    "Vehicles" : [
        {
            "$type" : "DemoApp.Motorbike",
            "Name" : "Ural",
            "MaxSpeed" : 160
        },
        {
            "$type" : "DemoApp.Motorbike",
            "Name" : "Harley-Davidson Hummer",
            "MaxSpeed" : 80
        },
        {
            "$type" : "DemoApp.Bus",
            "Name" : "Ikarus 280",
            "Manufacturer" : "Ikarus",
            "MaxPassengers" : 180
        }
    ]
}

From now server adds a type field and the client can deserialize DTO correctly.