One of the most powerful features in the Java JSON library Jackson, is support for polymorphic serialization. That is, when deserializaing a JSON (or XML) blob to a supertype, with proper configuration, an object of the correct subtype will be instantiated. Likewise, type information will be included in serialization.
This allows for Java clients and servers to communicate using JSON and use a common interface to share types.
// Animal.java import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonTypeInfo( use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type") @JsonSubTypes({ @JsonSubTypes.Type(name="cat", value=Cat.class), @JsonSubTypes.Type(name="dog", value=Dog.class) }) public interface Animal { String vocalize(); } // Dog.java public class Dog implements Animal { @Override String vocalize() { return "bark"; } } //Cat.java public class Cat implements Animal { @Override String vocalize() { return "meow"; } } //Example.java import com.fasterxml.jackson.databind.ObjectMapper; public class Example { private static final ObjectMapper MAPPER = new ObjectMapper(); public static main() { Animal jsonAnimal = MAPPER.readValue("{type=\"dog\"}", Animal.class); jsonAnimal.vocalize(); // "bark" Animal cat = new Cat(); MAPPER.writeValueAsString(cat); // "{type=\"cat\"}" } }
Typically this can be achieved using the JsonTypeInfo
and JsonSubTypes
annotation above. The JsonTypeInfo
annotation allows for considerable flexibility in how the type information is included in the serialized object.
However, I did find one situation where using the @JsonSubTypes
annotation directly on the interface is insufficient. This is when using <>AutoValue.
@AutoValue abstract public class Horse implements Animal { @Override String vocalize() { return "nay"; } static Animal create() { return new AutoValue_Horse(); } }
Adding @JsonSubTypes.Type(name="horse", value=Horse.class)
to Animal
will not provide the symmetric serialization we may expect here. Instead we’ll find the value for type
in our JSON markup will be the generated type AutoValue_Horse
and as a result we will not be able to deserialize the JSON blob to an `Animal` as that type isn’t known to the `Animal` interface.
The workaround here is adding additional type information to Horse
.
@AutoValue @JsonTypeName(Horse.JSON_TYPE_NAME) abstract public class Horse implements Animal { static final String JSON_TYPE_NAME = "horse"; ... } @JsonTypeInfo( use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type") @JsonSubTypes({ @JsonSubTypes.Type(name="cat", value=Cat.class), @JsonSubTypes.Type(name="dog", value=Dog.class), @JsonSubTypes.Type(name=Horse.JSON_TYPE_NAME, value=Horse.class) }) public interface Animal { String vocalize(); }
While it requires an additional annotation, adding JsonTypeName
to Horse
ensures that even the generated type AutoValue_Horse
will include the correct type name in the JSON markup so polymorphic serialization behaves as intended!