Deserializing different types based on properties, with Newtonsoft.Json
Sometimes we're presented objects in JSON that do not directly map to a strongly typed object. An example of that is given here:
[{
"type" : "Car",
"wheels" : 4,
"trunk" : true
}, {
"type" : "Bicycle",
"wheels" : 2,
"persons" : 1
}
]
This example is very simple, but it presents a fun little problem. How can we map the above into two types, Car
and Bicycle
, each deriving from a base class Vehicle
?
JsonConverter
The JsonConverter
is a helper class which can assist in converting other types than what Newtonsoft.Json comes with out of the box. They basically encompass the process of getting "json object -> .NET object" and back again. We'll be using one of these to map our types back and forth.
public class VehicleConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken jObject = JToken.ReadFrom(reader);
VehicleType type = jObject["type"].ToObject<VehicleType>();
Vehicle result;
switch (type)
{
case VehicleType.Car:
result = new Car();
break;
case VehicleType.Bicycle:
result = new Bicycle();
break;
default:
throw new ArgumentOutOfRangeException();
}
serializer.Populate(jObject.CreateReader(), result);
return result;
}
// Skipped WriteJson and CanConvert
}
In the implementation above, we switch the target type based on our reading of the VehiceType
enum value. We then use the Populate
method from the JsonSerializer
to populate our object, as calling Deserialize
would once more call our own Converter which eventually leads to a stack overflow [1].
We can adjust this approach any way we'd like. For example we could: Automatically find applicable types by searching the assembly, register our types dynamically through some management system etc.
Caveats
Currently, we can't serialize in a simple way. As with before, if we were to call Serialize directly, we'd end up calling ourselves. There is a workaround though - in my example I've registered the JsonConverter
directly on the Vehicle type. If we were instead to implement CanConvert
and register our converter with a JsonSerializerSettings
object, we'd be able to not use the converter when writing.
Example code
I've put the source code for the examples above online here: github.com/LordMike/blog-examples/deserialize-different-types.
[1] Actually, Newtonsoft detects this and throws a JsonSerializationException
, stating that a Self referencing loop detected