FasterXML / jackson-dataformat-xml

Extension for Jackson JSON processor that adds support for serializing POJOs as XML (and deserializing from XML) as an alternative to JSON
Apache License 2.0
568 stars 222 forks source link

Can't deserialize list in JsonSubtype when type property is visible #455

Open jimirocks opened 3 years ago

jimirocks commented 3 years ago

Using the latest 2.12.2 version. Getting com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No fallback setter/field defined for creator property 'ListItem' (through reference chain: cz.smarteon.loxone.system.status.Child["ListItem"]) when parent's @JsonTypeInfo(visible = true).

So it seems it's not possible to have the type property visible, while using collections in subtypes.

I also have more complex scenario, where the deserialization doesn't fail, but the list in subtype is simply not filled (which is even worse). However I wasn't able to make it short enough to include it in report, instead I found this. This could be also related to #426 ? (just a wild guess)

package test;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

import java.nio.charset.StandardCharsets;
import java.util.List;

public class VisibleTypePropertyTest {

    public static void main(String[] args) throws Exception {
        final ObjectMapper mapper = XmlMapper.builder().defaultUseWrapper(false).build();
        final String parentXml = "<Item type=\"parent\" someProperty=\"someValue\" />";
        final String childXml = "<Item type=\"child\" someProperty=\"someValue2\">" +
                "<ListItem name=\"a\"/><ListItem name=\"b\" />" +
                "</Item>";

        mapper.readValue(parentXml.getBytes(StandardCharsets.UTF_8), Parent.class);

        try {
            mapper.readValue(childXml.getBytes(StandardCharsets.UTF_8), Parent.class);
        } catch (InvalidDefinitionException ex) {
            System.out.println("This is the BUG");
            ex.printStackTrace();
        }
    }
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "type", defaultImpl = Parent.class
        , visible = true // this triggers the BUG
)
@JsonSubTypes({
        @JsonSubTypes.Type(name = "child", value = Child.class)
})
class Parent {
    final String visibleType;
    final String someProperty;

    @JsonCreator
    Parent(@JsonProperty("type") final String visibleType, @JsonProperty("someProperty") final String someProperty) {
        this.visibleType = visibleType;
        this.someProperty = someProperty;
    }
}

class Child extends Parent {

    final List<ChildItem> list;

    @JsonCreator
    Child(@JsonProperty("type") final String visibleType, @JsonProperty("someProperty") final String someProperty,
          @JsonProperty("ListItem") final List<ChildItem> list) {
        super(visibleType, someProperty);
        this.list = list;
    }
}

class ChildItem {
    @JsonCreator
    ChildItem(@JsonProperty("name") final String name) {}
}
jimirocks commented 3 years ago

Update, the problem only occurs with defaultUseWrapper(false) and the most probably is caused by

https://github.com/FasterXML/jackson-dataformat-xml/blob/5e928b2c8bce48dcb66be25c29bf35686b490b4f/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/WrapperHandlingDeserializer.java#L150

Here it is expected the JsonParser to be configured is either one passed as argument or the delegate one, however, in this scenario JsonParserSequence is passed which is changing the delegate when iterating through nextToken.

I guess other place for this configuration must be found, however it's beyond my knowledge

jimirocks commented 3 years ago

And moreover this BUG deosn't happen, when the type determining property is the first one (therefore JsonParserSequence is not used).

cowtowncoder commented 3 years ago

The problem is almost certainly due to buffering, then, necessary often when order of properties does not match what @JsonCreator needs: parser sequence is used, but mostly the issue is that TokenBuffer usage: XML deserialization code requires XML-specific parser and TokenBuffer is not one.

I hope this can be addressed in 2.13 as there is a plan to allow format-specific TokenBuffer sub-classes. That alone is not enough, but might allow a fix with xml-specific variant.