PortiBlog

Mapping a collection of interface based objects

13 december 2018

While building an API endpoint we had the requirement to handle a collection of objects based on an interface. This seemed relatively simple task but took more time then anticipated due to the JSON serialization and deserialization behavior.

When using a console application, you will get an error on deserializing a collection of interface types. The message will tell you “Could not create an instance of type x. Type is an interface or abstract class and cannot be instantiated”. When you are using an API and the input for the collection is for example the post data you will not get such an exception, you would just get an empty collection.

Doing a quick google search got me half way to the solution via the stackoverflow post: https://stackoverflow.com/questions/12638741/deserialising-json-to-derived-types-in-asp-net-web-api which was indicating to create a new class based on the JsonConverter. Using the accepted answer as a starter lead me to some more changes to be able to use it on objects where one of the properties is the problematic collection instead of the complete content as described in the post.

To demo this without needing to explain all surrounded business logic a sample of high school / alumni sports teams is used.

The API controller endpoint did not ask for a base class as is described in the post but an object that had a collection of classes as input:

// POST api/values
public void Post([FromBody]IEnumerable<Team> value)

The sample team object has the following properties:

public class Team
{
public string Name { get; set; }
public Sport Sport { get; set; }
public ICollection<IPerson> Players { get; set; }
}

When calling the Post API endpoint to create a new team the team would always have zero players. The default JsonConverter could not create objects based on the IPerson class, but instead of throwing an error like the console application it returned an empty collection.

In the sample code there can be an Alumni or a Student class present in the collection of players. To simplify the demo code, we added a string to each class to determine what the type is. A sample of the Alumni class is:

public class Alumni : IPerson
{
public string FirstName { get; set; }
public Sport FavoriteSport { get; set; }
public string PersonType => "Alumni";
}

To fix the deserialization we created the abstract class from the stackoverflow post. We created a PersonConverter class that implemented the abstract class and added the following code to the Create overload method to identify what kind of IPerson object was being deserialized:

protected override IPerson Create(Type objectType, JObject jObject)
{
switch (GetPersonType(jObject))
{
case "Alumni":
return new Alumni();
case "Student":
default:
return new Student();
}
}

The GetPersonType method checks if the property PersonType is available in the object and returns the value when available, or null when the property is not present. We choose to default to students when the property is not present.

To be able to activate this logic we needed to decorate the interface with an attribute to link it to the custom JsonConverter, there are no changes needed in the Team, Alumni or Student classes.

[JsonConverter(typeof(PersonConverter))]
public interface IPerson

After adding the custom JsonConverter to the IPerson interface the API did not have an empty collection anymore but objects of the correct typed, ready to be handled by the business logic.

Submit a comment