The basic issue that we are having is how to handle deserialization of JSON when there is a property that is typed as a base class that can be any one of several subclasses. There are many questions on this topic and the vast majority seem to point to the usage of the JSONSubTypes annotation on the baseclass.
The general example is something like the following, where Animals can be of several types - dog, cat, horse, etc.....
public class Pets {
private List<Animal> myPets;
public List<Animal> getMyPets(){
return myPets;
}
}
Buried in the comments is usually a comment arguing against the requirement to change the base class everytime you add a subclass. Consider the following example. If a class "Horse" was created and needed to be used a new type would need to be added to the list of JsonSubTypes for the Horse class.
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "Dog"),
@JsonSubTypes.Type(value = Cat.class, name = "Cat") }
)
public abstract class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The issue here is what if the base class and all the sub classes do not live in the same jar file? You now end up with a circular dependency. The sub classes have a dependency on the jar file with the base class and the base class has a dependency on the jar with the sub classes.
JAXB has a way to handle this by adding the list of subclasses on the property itself. Something like the following:
@XmlElements({@XmlElement(name="dog" type=Dog.class),
@XmlElement(name="cat" type=Cat.class)})
public List<Animals> getMyPets(){
return myPets;
}
Is there anyway to do this with Jackson or an alternative solution that doesn't require the subtypes to be added to the base class?
You may skip @JsonSubTypes
and listing subtypes by name, if you are happy putting (part of) the class name in JSON instead of a custom name you specify. E.g.
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY)
abstract class Animal {
Will result in a serialisation of Pets
like (note the dot before class name):
{"myPets":[{"@c":".Dog","name":"n"},{"@c":".Cat","name":"n"}]}
or with use = JsonTypeInfo.Id.CLASS
you also get the full package:
{"myPets":[
{"@class":"com.example.Dog","name":"n"},
{"@class":"com.example.Cat","name":"n"}
]}
Check docs for JsonTypeInfo
Alternatively, if all subclasses of Animal
are in the same package (different than Animal
) and you define ObjectMapper
in that, you can use mixins to specify subtypes and avoid "circular dependency":
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "Dog"),
@JsonSubTypes.Type(value = Cat.class, name = "Cat") }
)
interface AnimalMixIn {}
and
mapper.addMixIn(Animal.class, AnimalMixIn.class);
in this case there is no @JsonTypeInfo
, @JsonSubTypes
on Animal
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With