jackson-databind icon indicating copy to clipboard operation
jackson-databind copied to clipboard

Cannot use polymorphic type handling (DEDUCTION) with "POJO as Array" notation

Open platonef opened this issue 3 years ago • 1 comments

Problem (excuses for the long description) - Version: 2.13

I am trying to use the JsonTypeInfo.Id.DEDUCTION method which is described here and in a lot of other sources with JSON inputs which are an array, can have 3 different forms and looks like the following:

Form 1

[2, 
"Form1Text", 
"Form1MoreText",
{
"field": "value",
"nested": {
"nest1": "nest1_value,
"nest2": "nest2_value
}
}
]

Form 2

[3,
"Form2Text",
{
"form2Field1": "2013-02-01T20:53:32.486Z",
"form2Field2": 300,
"form2Field3": "Accepted"
}
]

Form 3

[4,
"Form3Text",
"Form3MoreText",
"Form3SomeMoreText",
{}
]

The content does not matter but the structure of the above messages and the fact that the root JSON element is not the classic JSON format (object with {}) but an array matter and I think here is the bug. Note that this is a perfect valid JSON message based on the JSON specification.

Structure of Form1

array with int, string, string, object (with property, property with object)

Structure of Form2

array with int, string, object (with properties)

Structure of Form3

array with int, string, string, string, empty object

Based on that input and the 3 possible structures I created the following classes

@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
@JsonSubTypes({
        @Type(value=Form1.class),
        @Type(value=Form2.class),
        @Type(value=Form3.class)
})
public abstract class CommonForm {
    public int formNumber;
    public String formText;

   // getter, setter, allArgConstructor, noArgConstructor
}

public class Form1 extends CommonForm {
    public String form1Text;
    public Map<String, Object> form1Map;

    // getter, setter, allArgConstructor, noArgConstructor
}

public class Form2 extends CommonForm {
    private  Map<String, Object> form2Map;

    // getter, setter, allArgConstructor, noArgConstructor
}

public class Form3 extends CommonForm {
    private String form3Text1;
    private String form3Text2;
    private String form3Text3;

    // getter, setter, allArgConstructor, noArgConstructor
}

The code to deserialize the input is the following

ObjectMapper mapper= new ObjectMapper();
String form1 = "[2,\"Form1Text\",\"Form1MoreText\",{\"field\": \"value\",\"nested\": {\"nest1\": \"nest1_value,\"nest2\": \"nest2_value}}]"; 
CommonForm cf = mapper.readValue(form1 , CommonForm.class);

By doing all the above and based on the deduction mechanism, I was expecting that the mapper will understand the inheritance and will read the JSON input (Form1 specifically in the above example) and return the object of class Form1 (the input JSON is a Form1). However, it does not recognize the inheritance (deduction) even though the classes can be uniquely distinguished and the following error occurs:

om.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (VALUE_NUMBER_INT), expected VALUE_STRING: need JSON String that contains type id (for subtype of com.example.form.CommonForm)
 at [Source: (String)"[2,"Form1Text","Form1MoreText",{"field": "value","nested": {"nest1": "nest1_value,"nest2": "nest2_value}}]"; line: 1, column: 2]
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
	at com.fasterxml.jackson.databind.DeserializationContext.wrongTokenException(DeserializationContext.java:1939)
	at com.fasterxml.jackson.databind.DeserializationContext.reportWrongTokenException(DeserializationContext.java:1673)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._locateTypeId(AsArrayTypeDeserializer.java:164)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:96)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromAny(AsArrayTypeDeserializer.java:71)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:170)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:96)
	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263)
	at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4675)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3630)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3598)

Based on the error message, it expects to have a field with the value type id. However, the purpose of the DEDUCTION mechanism is the opposite. The JSON input does not have a property/field that declares the value type id.

Is the problem the fact that the JSON input has a root element which is not the classic object({}) but an array([])? something else? or am I doing something wrong here regarding the use of the DEDUCTION mechanism?

Cheers, Platon

platonef avatar Feb 03 '22 22:02 platonef

I assume somewhere in code there is a setting to force use of Shape.ARRAY for POJOs? Otherwise JSON Array could not be serialization for POJOs.

If so, the simple answer is that use of Array serialization will limit support for all kinds of features, including (but not limited to) Object Id and Type Id handling. Polymorphic types do not (and probably can not) be supported with positional notation.

So: you can not use "POJO-as-array" along with Polymorphic Deserialization: this does not and will not work. The main question is that of how to document this limitation: I don't think it can ever be changed. And possibly producing better exception message if attempt is made to use the combination. This limitation is not specific to deduction but all modes of @JsonTypeInfo handling.

cowtowncoder avatar Feb 04 '22 22:02 cowtowncoder