Swagger and polymorphic type handling with Jackson

polimorphic types with jackson and swaggerPolymorphic 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:

@ApiModel(subTypes = {YahooProfile.class,GmailProfile.class}, discriminator = "provider")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "provider")
@JsonSubTypes({
    @Type(value = YahooProfile.class, name = YahooProfile.NAME),
    @Type(value = GmailProfile.class, name = GmailProfile.NAME)
})
abstract public class Profile {
    private String firstName;
    private String lastName;
   
    @XmlElement(name = "provider")
    @ApiModelProperty(required = true, allowableValues = "yahoo,gmail")
    abstract public String getProvider();
}

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.

@ApiModel(description = "GmailProfile", parent = Profile.class)
public class GmailProfile extends Profile {
    private final static String NAME = "gmail";

    @XmlElement(name = "provider")
    @ApiModelProperty(required = true)
    public String getProvider() {
        return NAME;
    }
}

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:

@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, include = JsonTypeInfo.As.PROPERTY, property = "provider")
@JsonTypeIdResolver(ProfileTypeResolver.class)
public abstract class Profile
{
    // common properties here
}

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:

public class ProfileTypeResolver implements TypeIdResolver
{
    private static final String PROFILE_PACKAGE = Profile.class.getPackage().getName() + ".profile";
    private JavaType baseType;

    @Override
    public void init(JavaType baseType)
    {
        this.baseType = baseType;
    }

    @Override
    public Id getMechanism()
    {
        return Id.CUSTOM;
    }

    @Override
    public String idFromValue(Object obj)
    {
        return idFromValueAndType(obj, obj.getClass());
    }

    @Override
    public String idFromBaseType()
    {
        return idFromValueAndType(null, baseType.getRawClass());
    }

    @Override
    public String idFromValueAndType(Object obj, Class<?> clazz)
    {
        String name = clazz.getName();
        if ( name.startsWith(PROFILE_PACKAGE) ) {
            return name.substring(PROFILE_PACKAGE.length() + 1);
        }
        throw new IllegalStateException("class " + clazz + " is not in the package " + PROFILE_PACKAGE);
    }

    @Override
    public JavaType typeFromId(DatabindContext context, String id)
    {
        Class<?> clazz;
        String clazzName = PROFILE_PACKAGE + "." + type;
        try {
            clazz = ClassUtil.findClass(clazzName);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("cannot find class '" + clazzName + "'");
        }
        return context.constructSpecializedType(baseType, clazz);
    }
}

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!

It's only fair to share...Buffer this pageShare on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn1Share on Reddit0Pin on Pinterest0Email this to someone
About

Just a guy with strong interest in PHP and Web technologies

Posted in API, Java, Spring MVC, Swagger Tagged with: , , , , , ,
One comment on “Swagger and polymorphic type handling with Jackson
  1. robocide says:

    great article ! why not share code ? 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

*