Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make @JsonTypeInfo property optional

Tags:

java

json

jackson

I'm using @JsonTypeInfo to instruct Jackson to look in the @class property for concrete type information. However, sometimes I don't want to have to specify @class, particularly when the subtype can be inferred given the context. What's the best way to do that?

Here's an example of the JSON:

{ 
    "owner": {"name":"Dave"},
    "residents":[
        {"@class":"jacksonquestion.Dog","breed":"Greyhound"},
        {"@class":"jacksonquestion.Human","name":"Cheryl"},
        {"@class":"jacksonquestion.Human","name":"Timothy"}
    ]
}

and I'm trying to deserialize them into these classes (all in jacksonquestion.*):

public class Household {
    private Human owner;
    private List<Animal> residents;

    public Human getOwner() { return owner; }
    public void setOwner(Human owner) { this.owner = owner; }
    public List<Animal> getResidents() { return residents; }
    public void setResidents(List<Animal> residents) { this.residents = residents; }
}

public class Animal {}

public class Dog extends Animal {
    private String breed;
    public String getBreed() { return breed; }
    public void setBreed(String breed) { this.breed = breed; }
}

public class Human extends Animal {
    private String name;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

using this config:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
private static class AnimalMixin {
}

//...

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.getDeserializationConfig().addMixInAnnotations(Animal.class, AnimalMixin.class);
Household household = objectMapper.readValue(json, Household.class);
System.out.println(household);

As you can see, the owner is declared as a Human, not an Animal, so I want to be able to omit @class and have Jackson infer the type as it normally would.

When I run this though, I get

org.codehaus.jackson.map.JsonMappingException: Unexpected token (END_OBJECT), 
   expected FIELD_NAME: missing property '@class' that is to contain type id  (for class jacksonquestion.Human)

Since "owner" doesn't specify @class.

Any ideas? One initial thought I had was to use @JsonTypeInfo on the property rather than the type. However, this cannot be leveraged to annotate the element type of a list. (Incorrect, see answer)

like image 434
Mark Peters Avatar asked Oct 23 '25 02:10

Mark Peters


1 Answers

So it turns out that I had misunderstood the Javadoc for @JsonTypeInfo. When I said in my question

One initial thought I had was to use @JsonTypeInfo on the property rather than the type. However, this cannot be leveraged to annotate the element type of a list.

I was basing that on this quote from the Javadoc:

When used for properties (fields, methods), this annotation applies to values: so when applied to structure types (like Collection, Map, arrays), will apply to contained values, not the container; for non-structured types there is no difference. (...) There is no per-property way to force type information to be included for type of container (structured type); for container types one has to use annotation for type declaration.

I somehow misread that to mean the opposite; that the type info would apply to the container and not the elements. Obviously that was wrong. So I was able to fix this by using the following:

public class Household {
   //...

   @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
   public void setResidents(List<Animal> residents) { 
      this.residents = residents; 
   }
}

Now @class is only required for Animals that are specified in the residents property.

like image 190
Mark Peters Avatar answered Oct 24 '25 17:10

Mark Peters