Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specifying Jackson JSON subtypes on something other than the base class due to circular dependency

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?

like image 365
Todd Patch Avatar asked Mar 07 '18 14:03

Todd Patch


1 Answers

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

like image 89
Manos Nikolaidis Avatar answered Nov 11 '22 22:11

Manos Nikolaidis