I have the following schemas for JSON objects for some endpoints:
{ // Dog.java
"name": "Fido",
"age": 5,
"barkLoudness": 8,
"treatsToday": 2
}
{ // Cat.java
"name": "Fluffy",
"age": 2,
"color": "white",
}
{ // Fish.java
"name": "Professor",
"age": 1,
"blub": "blub"
}
All of the schemas share the name
and age
field, but can extend it with their own properties. When deserializing, the object mapper will know what type its deserializing too, such as a POST to /dog
invoking mapper.readValue(input, Dog.class)
. What I'm struggling with is how to define the classes, such that I don't have to repeat the name
and age
field with all of them:
abstract class Animal<T> { // abstract? interface? concrete?
private final String name;
private final int age;
@JsonUnwrapped
private final T info;
// unsure if Unwrapped works here
@JsonCreator
protected Animal(
@JsonProperty("name") String name,
@JsonProperty("age") int age,
@JsonUnwrapped T info
) {
this.name = name;
this.age = age;
this.info = info;
}
// getters
}
public class Dog extends Animal<DogInfo> {
private final int barkLoudness;
private final int treatsToday;
// Don't want to have to repeat the name/age in concrete subtypes
// or the JsonProperty annotation
@JsonCreator
public Dog(
@JsonProperty("name") String name,
@JsonProperty("age") int age,
@JsonProperty("barkLoudness") int barkLoudness,
@JsonProperty("treatsToday") int treatsToday
) {
super(name, age);
this.barkLoudness = barkLoudness;
this.treatsToday = treatsToday;
}
// getters
}
If it helps, I'm never going to have a method such as process(Animal<T> animal)
, I'll always be working with a concrete type. I want to make all objects immutable, and to not repeat the age
and name
fields/definition between all the objects. I have a small preference to not use the @JsonTypeInfo
annotation, but if that's the solution I'm fine with it. Is there a way to create the class structure so this "inheritance" can be achieved?
Using inheritance and the @JsonProperty
annotation, you can prevent having to repeat age
and name
between all the objects:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
abstract class Animal {
private final String name;
private final int age;
@JsonCreator
protected Animal(@JsonProperty("name") String name, @JsonProperty("age") int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
final class Dog extends Animal {
private final int barkLoudness;
private final int treatsToday;
@JsonCreator
public Dog(
@JsonProperty("name") String name,
@JsonProperty("age") int age,
@JsonProperty("barkLoudness") int barkLoudness,
@JsonProperty("treatsToday") int treatsToday
) {
super(name, age);
this.barkLoudness = barkLoudness;
this.treatsToday = treatsToday;
}
public int getBarkLoudness() {
return barkLoudness;
}
public int getTreatsToday() {
return treatsToday;
}
}
final class Cat extends Animal {
private final String color;
@JsonCreator
public Cat(
@JsonProperty("name") String name,
@JsonProperty("age") int age,
@JsonProperty("color") String color
) {
super(name, age);
this.color = color;
}
public String getColor() {
return color;
}
}
final class Fish extends Animal {
private final String blub;
@JsonCreator
public Fish(
@JsonProperty("name") String name,
@JsonProperty("age") int age,
@JsonProperty("blub") String blub
) {
super(name, age);
this.blub = blub;
}
public String getBlub() {
return blub;
}
}
public class Main {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
String dogJson = """
{
"name": "Fido",
"age": 5,
"barkLoudness": 8,
"treatsToday": 2
}
""";
Dog dog = mapper.readValue(dogJson, Dog.class);
System.out.println("Dog: " + dog.getName() + ", Age: " + dog.getAge() +
", Bark Loudness: " + dog.getBarkLoudness() +
", Treats Today: " + dog.getTreatsToday());
String catJson = """
{
"name": "Fluffy",
"age": 2,
"color": "white"
}
""";
Cat cat = mapper.readValue(catJson, Cat.class);
System.out.println("Cat: " + cat.getName() + ", Age: " + cat.getAge() +
", Color: " + cat.getColor());
String fishJson = """
{
"name": "Professor",
"age": 1,
"blub": "blub"
}
""";
Fish fish = mapper.readValue(fishJson, Fish.class);
System.out.println("Fish: " + fish.getName() + ", Age: " + fish.getAge() +
", Blub: " + fish.getBlub());
}
}
Output:
Dog: Fido, Age: 5, Bark Loudness: 8, Treats Today: 2
Cat: Fluffy, Age: 2, Color: white
Fish: Professor, Age: 1, Blub: blub