When dealing with complex data structures that involves classes and sub-classes in Java that require JSON serialization and deserialization, managing polymorphism can be challenging. Fortunately, the Jackson library provides powerful annotations like @JsonTypeInfo and @JsonSubTypes that simplify this task.

Jackson Annotations

Jackson’s annotations @JsonTypeInfo and @JsonSubTypes are pivotal for handling polymorphic types. They enable Jackson to serialize subclass types accurately and deserialize JSON back into the correct Java subclasses.

@JsonTypeInfo Usage

The @JsonTypeInfo annotation helps specify metadata about how subclasses are identified in the JSON output. Key attributes include:

  • use: Defines how subclass identification is handled (NAME for logical names or CLASS for fully-qualified Java class names).
  • include: Specifies where in the JSON the type information should appear (PROPERTY for within the JSON object, WRAPPER_OBJECT for around the JSON object).
  • property: Sets the name of the property where type information will reside.
  • visible: The visible property within the context of the @JsonTypeInfo annotation in Jackson controls whether the type identifier property will be visible as regular JSON property in serialized JSON output and can be accessed during deserialization. This property is particularly useful when you want the type information to be available for other operations beyond just determining the subclass during deserialization. By Default this is false.

@JsonSubTypes Usage

Accompanying @JsonTypeInfo, the @JsonSubTypes annotation declares available subclasses

@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "dog"),
    @JsonSubTypes.Type(value = Cat.class, name = "cat")
})

Example

Defining Your Java Classes with Jackson Annotations

Start by defining an abstract Animal class and its subclasses, Dog and Cat. By using Jackson annotations, you can instruct the framework on how to handle these subclasses during the JSON conversion process.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "dog"),
    @JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public abstract class Animal {
    public String name;

    public Animal(String name) {
        this.name = name;
    }
}

Serializing Subclasses into JSON

Create instances of Dog and Cat, serialize them, and examine the output to ensure the type information is included as expected.

Dog dog = new Dog("Buddy", "Loud");
Cat cat = new Cat("Whiskers", 9);

ObjectMapper mapper = new ObjectMapper();
String dogJson = mapper.writeValueAsString(dog);
String catJson = mapper.writeValueAsString(cat);

System.out.println("Serialized Dog JSON: " + dogJson);
System.out.println("Serialized Cat JSON: " + catJson);

Deserializing JSON Back into Java Objects

Using the JSON strings, deserialize them back into their respective subclasses to verify that Jackson correctly interprets and processes the type information.

String dogJson = "{\"type\":\"dog\",\"name\":\"Buddy\",\"barkStyle\":\"Loud\"}";
String catJson = "{\"type\":\"cat\",\"name\":\"Whiskers\",\"lives\":9}";

Animal dog = mapper.readValue(dogJson, Animal.class);
Animal cat = mapper.readValue(catJson, Animal.class);