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 (
NAMEfor logical names orCLASSfor fully-qualified Java class names). - include: Specifies where in the JSON the type information should appear (
PROPERTYfor within the JSON object,WRAPPER_OBJECTfor around the JSON object). - property: Sets the name of the property where type information will reside.
- visible: The
visibleproperty within the context of the@JsonTypeInfoannotation 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);