Skip to content

Order of XML Properties trigger different behaviors with polymorphic nested objects  #525

Open
@vitorpamplona

Description

@vitorpamplona

This very specific structure fails to parse a correct XML when the xsi:type property is placed after other fields in the XML. That's the only difference between the test cases.

See below:

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
import org.testng.annotations.Test;
import javax.xml.bind.annotation.*;
import java.io.IOException;
import java.util.List;

import static org.junit.Assert.assertEquals;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ModelInfo")
class ModelInfo {
    protected List<TypeInfo> typeInfo;
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "TypeInfo")
@XmlSeeAlso({ ClassInfo.class })
abstract class TypeInfo {

}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ClassInfo")
class ClassInfo extends TypeInfo {
    protected List<ClassInfoElement> element;
    @XmlAttribute(required = true)
    protected String name;
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ClassInfoElement")
class ClassInfoElement {
    protected BindingInfo binding;
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BindingInfo")
class BindingInfo {
    protected String description;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = ClassInfo.class)
})
interface TypeInfoMixIn {}

public class JacksonXMLTests {

    XmlMapper mapper = new XmlMapper().builder()
            .defaultUseWrapper(false)
            .addMixIn(TypeInfo.class, TypeInfoMixIn.class)
            .addModule(new JaxbAnnotationModule())
            .build();

    @Test
    public void testTypeAfterOtherProperties() throws IOException {
        String xml =
                "<modelInfo xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
                "  <typeInfo name=\"MyName\" xsi:type=\"ClassInfo\">\n" +
                "    <element>\n" +
                "      <binding description=\"Test\"/>\n" +
                "    </element>\n" +
                "  </typeInfo>\n" +
                "</modelInfo>";

        ModelInfo m = mapper.readValue(xml, ModelInfo.class);
        assertEquals("MyName", ((ClassInfo)m.typeInfo.get(0)).name);
        assertEquals("Test", ((ClassInfo)m.typeInfo.get(0)).element.get(0).binding.description);
    }

    @Test
    public void testTypeBeforeOtherProperties() throws IOException {
        String xml =
                "<modelInfo xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
                "  <typeInfo xsi:type=\"ClassInfo\" name=\"MyName\">\n" +
                "    <element>\n" +
                "      <binding description=\"Test\"/>\n" +
                "    </element>\n" +
                "  </typeInfo>\n" +
                "</modelInfo>";

        ModelInfo m = mapper.readValue(xml, ModelInfo.class);
        assertEquals("MyName", ((ClassInfo)m.typeInfo.get(0)).name);
        assertEquals("Test", ((ClassInfo)m.typeInfo.get(0)).element.get(0).binding.description);
    }
}

Running this with:

        compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.2'
        compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.13.2'
        compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.13.2'
        compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-jaxb-annotations', version: '2.13.2'

Yields a pass on testTypeBeforeOtherProperties and a fail on testTypeAfterOtherProperties with the following stack:

Unrecognized field "description" (class org.cqframework.cql.cql2elm.ClassInfoElement), not marked as ignorable (one known property: "binding"])
 at [Source: (StringReader); line: 4, column: 36] (through reference chain: org.cqframework.cql.cql2elm.ModelInfo["typeInfo"]->java.util.ArrayList[0]->org.cqframework.cql.cql2elm.ClassInfo["element"]->java.util.ArrayList[0]->org.cqframework.cql.cql2elm.ClassInfoElement["description"])
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "description" (class org.cqframework.cql.cql2elm.ClassInfoElement), not marked as ignorable (one known property: "binding"])
 at [Source: (StringReader); line: 4, column: 36] (through reference chain: org.cqframework.cql.cql2elm.ModelInfo["typeInfo"]->java.util.ArrayList[0]->org.cqframework.cql.cql2elm.ClassInfo["element"]->java.util.ArrayList[0]->org.cqframework.cql.cql2elm.ClassInfoElement["description"])
	at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:1127)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1989)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1700)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1678)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:319)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:355)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
	at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:214)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:186)
	at com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer.deserialize(WrapperHandlingDeserializer.java:122)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:144)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:110)
	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:357)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
	at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176)
	at com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer.deserialize(WrapperHandlingDeserializer.java:122)
	at com.fasterxml.jackson.dataformat.xml.deser.XmlDeserializationContext.readRootValue(XmlDeserializationContext.java:91)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3597)
	at org.cqframework.cql.cql2elm.JacksonXMLTests.testTypeAfterOtherProperties(JacksonXMLTests.java:72)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
	at org.testng.TestRunner.privateRun(TestRunner.java:767)
	at org.testng.TestRunner.run(TestRunner.java:617)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:348)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:343)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:305)
	at org.testng.SuiteRunner.run(SuiteRunner.java:254)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
	at org.testng.TestNG.run(TestNG.java:1057)
	at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.runTests(TestNGTestClassProcessor.java:141)
	at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.stop(TestNGTestClassProcessor.java:90)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy5.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:132)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:412)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.base/java.lang.Thread.run(Thread.java:834)

I am not sure if this is a JAXB Module issue or a DataFormat issue. I hope this is the right place.

Metadata

Metadata

Assignees

No one assigned

    Labels

    has-failing-testIndicates that there exists a test case (under `failing/`) to reproduce the issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions