Custom Jackson Polymorphic Deserialization Without Type Metadata

At SportsLabs we regularly encounter proprietary, non-standard APIs and formats. Our job is to integrate with these APIs, normalize them, and distribute the data in web- and mobile-friendly web services.

One of the scenarios we often encounter is a provider supplying multiple resource JSON-based APIs that share a lot of the same data in their responses, but without any particular field dedicated to identying the type of the resource e.g.

1
2
3
4
5
6
{
...
  "common": "a common field within multiple resource responses",
  "one": "one is a field only within this response type"
...
}

and

1
2
3
4
5
6
{
...
  "common": "a common field within multiple resource responses",
  "two": "two is a field only within this response type"
...
}

Instead of mapping 1-to-1 with these APIs, we often try to follow DRY principles and model them as implementations of a common polymorphic abstraction.

When using Jackson for polmorphic deserialization and not being in control of the API response data, the lack of any kind of type identifier requires a different solution.

One of the ways we’ve addressed this problem is to identify fields and properties that are unique to a particular resource API’s response.

We then add this field to a registry of known unique-property-to-type mappings and then, during deserialization, lookup the response’s field names to see if any of them are stored within the registry.

Planning out the solution, there were a couple of things I wanted to incorporate:

  • the deserializer should be initialized with the abstract class representing the shared response data
  • a register(String uniqueProperty, Class<? extends T> clazz) method will add the field-name-to-concrete-class mapping to the registry
  • the custom deserializer would be added to a Jackson SimpleModule which in turn would be registered with the ObjectMapper.

The deserialize method reads each of the fields in the response and looks up the registry to see if it is present. If it finds a match, the mapper.treeToValue method is invoked with the response object and the mapped class returned by the registry. If no match is found an exception is thrown.

For the unit test, I created an inner static abstract class AbstractTestObject (containing shared data) with two concrete implementations (TestObjectOne and TestObjectTwo) that each contain a property unique to that type.

The test also contains a inner class TestObjectDeserializer that extends UniquePropertyPolymorphicDeserializer. The test’s setUp method:

  • initializes the custom deserializer,
  • registers the unique-field-name-to-type mappings, and
  • adds the custom deserializer to a new SimpleModule which is registered with the ObjectMapper in turn.

If others have solved this problem differently, I’d love to hear about it!

Comments