Swagger and polymorphic type handling with Jackson
Polymorphic type handling based on JSON property
Building an API that supports inheritance could be quite crucial. Imagine the case where you want to have an endpoint that stores a profile data for a given provider and that provider could be your main website or any 3rd party platform that can provide the data. We might have a base Profile class which in our case would be abstract class with firstName and lastName fields and an abstract method to provide the name of the actual profile provider getProfile().
You can do all of that quite easily with few Jackson annotations, for polymorphic types, on the base class and on the children that extend the base class. No more words here is an example:
The @JsonTypeInfo allows us to configure how type information is used with JSON serialization and deserialization and what strategy we want to use to resolve the hard implementation of the class based on the JSON data. In our case we want to resolve on a string field inside the JSON data and more specifically the provider field. We have few options here, in use you can set it to class and need to provide either fully qualified class name or short class name I prefer to use a string representation JsonTypeInfo.Id.NAME as that makes it easier to read and store in DB for example.
Second part is the include which specifies that we want to use JSON propety JsonTypeInfo.As.PROPERTY and in the property tag we provide the name of the field we want to take the data from, in our case provider.
That’s all for the parent class we now need two hard implementations that will provide different functionality for the two profiles.
In the GmailProfile we define the name of the class which will be used to map the JSON data to the hard implementation. Here there are no Jackson specific annotations just few for Swagger to provide nice documentation on the test UI. If we have extra properties that need to be set for one or another sub-class Jackson will automatically inspect the setters of the class and set all applicable values on the fly.
This approach can be applied for both Request and Response objects in our API.
Jackson custom type resolver
But what if neither the Class-based nor the property-based @JsonSubType default type ID resolvers are fitting your use case?
Enter custom type ID resolvers! In my case a server returned an identifier for a Profile that I wanted to match one-to-one on a specific “Sub-Profile” class without having to configure each of these identifiers in a @JsonSubType configuration. Furthermore each of these sub-profiles should live in the .profile package beneath the base profile class. So here is what I came up with:
The important part beside the additional @JsonTypeIdResolver annotation is the use argument that is set to JsonTypeInfo.Id.CUSTOM. Normally you’d use JsonTypeInfo.Id.CLASS or JsonTypeInfo.Id.NAME. Lets see how the ProfileTypeResolver is implemented:
The two most important methods here are idFromValueAndType and typeFromId. For the first I get the class name of the class to serialize and check whether it is in the right package (the .profile package beneath the package where the Profile class resides). If this is the case, I strip-off the package path and return that to the serializer. For the latter method I go the other way around: I try to load the class with Jackson’s ClassUtils by using the class name I got from the deserializer and prepend the expected package name in front of it. And that’s already it!