
Deserializing abstract types using Newtonsoft.Json
Recently I had to integrate with a “delivery” web service that provided it’s own contracts as part of a NuGet package. My immediate thought was “sweet, now I don’t have to do all of that boring typing to add all of the requisite types”. That was until I ran into some code that looks similar to this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public abstract class BaseDeliverable { } public class CollectionDeliverable : BaseDeliverable { public string CollectionReference { get; set; } } public class QrCodeDeliverable : BaseDeliverable { public List<string> TicketIds { get; set; } public string QrCode { get; set; } } public class RequiresDocumentsDeliverable : BaseDeliverable { public List<Document> Documents { get; set; } } public class Delivery { public string Id { get; set; } public string Type { get; set; } public string State { get; set; } public string DisplayName { get; set; } public string Reference { get; set; } public List<BaseDeliverable> Deliverables { get; set; } } |
The problem
When making calls to the delivery web service, the response was expected to be of type Delivery
which is shown above. The problem is that the Newtonsoft.Json serializer that WebApi uses by default is not able to determine which subclass of BaseDeliverable
to use and therefore fails to deserialize the response. While this sucks for me, it’s not the fault of Json.Net as .
My first instinct was to change these contracts entirely so that these objects that were related from a business point of view were not so closely related in the code. That would have involved removing of the base class and adding a property to Delivery
for each of the different types of delivery. Unfortunately, despite being the only consumer of the NuGet package, I wasn’t the only consumer of the service and the service itself used this contract package for (correctly) serializing responses to be sent to other clients.
The second option would be to change the delivery service to output type information but again, that would break the contract with the other consumers.
The solution
As I was not able to change the contract itself, I decided to change how the contract would be created when getting a response from the delivery web service. This led me to look into custom converters to see if I could determine which subclass to deserialize to based on the properties that the JSON object has.
After much Googling, I stumbled upon this article which had the majority of the code that I needed but I wanted to make it a bit more generic. Here’s my custom JsonConverter
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
public class SubclassConverter : JsonConverter { private readonly Type _convertSubtypesOfType; private readonly IEnumerable<Type> _subtypes; public SubclassConverter(Type convertSubtypesOfType) { _convertSubtypesOfType = convertSubtypesOfType; _subtypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(domainAssembly => domainAssembly.GetTypes(), (domainAssembly, assemblyType) => assemblyType) .Where(t => _convertSubtypesOfType.IsAssignableFrom(t) && t != _convertSubtypesOfType && !t.IsAbstract); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType != JsonToken.StartObject) return existingValue; var obj = JObject.Load(reader); objectType = GetSubtypeToConvertTo(obj); existingValue = Activator.CreateInstance(objectType); serializer.Populate(obj.CreateReader(), existingValue); return existingValue; } public override bool CanConvert(Type objectType) { return objectType.IsAssignableFrom(_convertSubtypesOfType); } public override bool CanRead => true; public override bool CanWrite => false; private Type GetSubtypeToConvertTo(JObject jObj) { foreach (var subtype in _subtypes) { if (JsonHasPropertiesFromType(subtype, jObj)) return subtype; } return _convertSubtypesOfType; } private static bool JsonHasPropertiesFromType(Type t, JObject json) { var jsonProperties = json.Properties().Select(jProp => jProp.Name.ToLowerInvariant()); return t.GetProperties().Select(prop => prop.Name.ToLowerInvariant()).All(propName => jsonProperties.Contains(propName)); } } |
One of the key differences to the example given in the linked article is that this converter takes a Type
as a constructor parameter. When constructing the converter, all subtypes of the specified type will be stored. In ReadJson
, we determine which type to deserialize to by trying to match all of the public property names on each of the subtypes with the JSON field names present. In this implementation there must be an exact match but this could easily be changed to be more forgiving.
The major learning from the linked article is that you cannot use obj.ToObject<T>(reader)
or JsonConvert.DeserializeObject<T>(obj.ToString())
here because the same converter will be instantiated and called resulting in an infinite loop until a stack overflow is thrown! The method shown creates a new instance of the type that we’ve decided that we want and then populates that instance using the serializer which avoids calling the custom converter. Clever!
Conclusion
To be honest, I wish I didn’t have to write this code but it does solve a problem that was blocking me from adding value to the client’s product! I’ve written this post mainly as a reference for myself so I don’t have to do so much Googling if I am ever stuck needing to do this again!