Jackson JsonTypeInfo.As.EXTERNAL_PROPERTY doesn't work as expected
Asked Answered
L

2

38

I am using Jackson to parse JSON that I have no control over. The JSON looks like this:

{
    "status":"0"
    "type":"type1"
    "info": {
       // additional fields
    }
}

My class looks like this

public class Response {
    private String status;
    private String type;
    private Info info
}

The subclass of Info that I use depends on the type property, so my mapping for info is

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
@JsonSubTypes(value = {
        @JsonSubTypes.Type(value = Type1Info.class, name = "type1"),
        @JsonSubTypes.Type(value = Type2Info.class, name = "type2") })
public abstract class Info {
    // some fields
}

As far as I can tell this is the correct way to use type info when the distinguishing element is at the same level as the element that has to be casted. But this doesn't work, I always get the same error:

com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'type' that is to contain type id

If I change EXTERNAL_PROPERTY to PROPERTY I still get the same error. Is my understanding of EXTERNAL_PROPERTY wrong?

Lectra answered 12/9, 2013 at 6:57 Comment(2)
I have this exact issue with my conception of how EXTERNAL_PROPERTY is supposed to work. Did you make any headway on this?Quetzalcoatl
Unfortunately not Marc. We ended up changing the object model completely to avoid this problem.Lectra
S
62

From Javadoc:

Inclusion mechanism similar to PROPERTY, except that property is included one-level higher in hierarchy, i.e. as sibling property at same level as JSON Object to type. Note that this choice can only be used for properties, not for types (classes). Trying to use it for classes will result in inclusion strategy of basic PROPERTY instead.

Noticed that can only be used for properties is bolded. Source: JsonTypeInfo.As.EXTERNAL_PROPERTY.

So, you have to move all annotation from Info class to property info or setInfo method in Response class.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
@JsonSubTypes(value = { @JsonSubTypes.Type(value = Type1Info.class, name = "type1"),
        @JsonSubTypes.Type(value = Type2Info.class, name = "type2") })
public void setInfo(Info info) {
    this.info = info;
}

For me, you should also remove type property from Response class. It will be generated dynamically during serialization process. In deserialization you do not need it because Jackson cares about types. Your class could look like this:

class Response {

    private String status;
    private Info info;

    //getters, setters
}

See also this question: JSON nest class data binding.

Satiable answered 3/1, 2014 at 23:28 Comment(4)
I read the JavaDoc, but didnt understand, that the java class property was supposed. Thanks!Adur
Thank you! you saved me there. I actually read the part on "can only be used for properties" multiple times but didn't link it to mean that I have to move the annotation to the property level.Jodeejodhpur
gist.github.com/sscovil/8788339#gistcomment-1461293 was able to help me.Dizon
Thank you! In my case deleting the redundant property type helpedTumbling
S
0

For all those who use AutoValue or any other source code generator: @JsonTypeInfo and @JsonSubTypes annotations does not work when you annotate your abstract methods (which in generated code returns the actual property). If you do it this way, you will get following exception: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of [xxx] (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

Also it not works when you annotate parameter in your method annotated as @JsonCreator. If you do it this way you would get this weird exception: com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class [xxx]

So to deal with it just dont use source code generator for this class, just map the Response object using common jackson annotations: @JsonCreator, @JsonProperty and @JsonTypeInfo, @JsonSubTypes

tested with jackson 2.10.5

Shrike answered 14/6, 2022 at 10:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.