Class inheritance and polymorphism in JSON schema

Jason Ge
4 min readSep 28, 2021

--

In C# web API, we use JSON to pass the data back and forth. These JSON data would be deserialized to C# object. Most of time, it is straightforward. However, if the C# object has class inheritance structure and polymorphism, it would be much tricky how to define the JSON schema.

Let’s make an example, we have a Person class, an abstract BasePet class (base class), a Cat class (inherited from BasePet class) and a Dog class (inherited from BasePet class). The Person class has a property called Pet, which is defined as BasePet type. However, during the run time, the Pet property may be a Cat or Dog. How do we define the JSON schema and C# class (auto generated from the JSON schema) to achieve this?

Inheritance in JSON: allOf

The inheritance is achieved in JSON by the keyword allOf. This keyword validates the value against all the subschemas. In the sub class definition, we would put the reference to the base type first followed by the new properties in the sub class. Below is the definition of Cat class in .yaml format.

However, class inheritance is not enough. Since the Person class only has Pet property as BasePet type, the JSON serialization/deserialization process does not know if the Pet property is actually Cat or Dog type. We need add this information into the JSON schema definition.

Polymorphism in JSON: discriminator

In the swagger web site, it says to use the oneOf or anyOf keywords to implement polymorphism. However, I am not able to get it working. Instead, I find the keyword discriminator works perfectly for me. In the base class, we need to define a property called petType and use discriminator keyword to point to this property name. The C# class generated from the JSON schema would include correct attributes in the base class. These attributes would help web api to identify what sub class it is in the JSON data.

discriminator keyword

Following is the full JSON schema definition in yaml format:

Gist link here.

Generate C# classes

Once we have the JSON schema definition (either JSON or YAML format), we can import to https://editor.swagger.io/ to validate and generate classes for clients and server.

Generate C# client code

BasePet class

Cat class

Dog class

Person class

JsonSubTypes

Behind the scene, the Swagger Editor uses JsonSubTypes attribute to decorate the BasePet class. You can find the detailed document about JsonSubTypes in Github: https://github.com/manuc66/JsonSubTypes

The following 3 attributes are applied to the BasePet class:

  1. [JsonConverter(typeof(JsonSubtypes), “petType”)]: This attribute tell the framework the JsonSubtypes is based on the property called “PetType”.
  2. [JsonSubtypes.KnownSubType(typeof(Cat), “Cat”)]: This attribute creates a map between the Cat class and the “Cat” value in PetType property, i.e., if the PetType property value is “Cat”, framework will try to deserialize the JSON string to Cat object.
  3. [JsonSubtypes.KnownSubType(typeof(Dog), “Dog”)]: This attribute creates a map between the Dog class and the “Dog” value in PetType property, i.e., if the PetType property value is “Dog”, framework will try to deserialize the JSON string to Dog object.

Note: JsonSubTypes depends on Newtonsoft.Json to work. So please make sure the framework is using Newtonsoft.Json to serialize/deserialize JSON data. In Asp.net core 3.1, the default Json serializer is JsonSerializer in System.Text.Json namespace. JsonSubTypes does not work with JsonSerializer in System.Text.Json namespace. You have to switch back to use Newtonsoft.Json in startup.cs.

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddNewtonsoftJson();
}

I have created a testing solution consisting a web api project and console project. Both projects use the classes generated from the Swagger as models. The console project will call web api and pass a Person object with its Pet property pointing to a Dog object. As you can see from the runtime, the web api can successfully deserialize the Pet property to Dog object.

[HttpPost(“Update”)]
public Person Update(Person person)
{
if (person.Pet is Dog)
{
_logger.LogInformation(“Received a person with pet Dog”);
}
else if (person.Pet is Cat)
{
_logger.LogInformation(“Received a person with pet Cat”);
}
else
{
_logger.LogWarning(“Received a person with pet BasePet”);
}
return person;
}

You can download the demo solution in Github here: https://github.com/jason-ge/JsonPolymorphism

Happy coding!

--

--

Jason Ge
Jason Ge

Written by Jason Ge

Software developer with over 20 years experience. Recently focus on Vue/Angular and asp.net core.

Responses (1)