javaxmljacksonfasterxml

Deserialize list of objects from XML to Java POJOs using Jackson


This is my XML:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<licenseSummary>
  <dependencies>
    <dependency>
      <groupId>Hello World 1</groupId>
    </dependency>
    <dependency>
      <groupId>Hello World 2</groupId>
    </dependency>
  </dependencies>
</licenseSummary>

This is my model:

@Getter
@Setter
@JacksonXmlRootElement(localName = "licenseSummary")
@EqualsAndHashCode
public class LicenseSummary {
    @NonNull
    @JacksonXmlProperty(localName = "dependency")
    @JacksonXmlElementWrapper(localName = "dependencies")
    List<Dependency> dependencies;

    @JsonCreator
    public LicenseSummary(@NonNull List<Dependency> dependencies) {
        this.dependencies = dependencies;
    }
}
@Getter
@Setter
@EqualsAndHashCode
public class Dependency {
    @NonNull
    @JacksonXmlProperty(localName = "groupId")
    private String groupId;

    @JsonCreator
    public Dependency(@NonNull String groupId) {
        this.groupId = groupId;
    }
}

When I try to deserialize the above XML into the model POJOs, I get the following error:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deseria    ```lize value of type `java.util.ArrayList<de.symate.maven.plugins.licenseaggregator.model.Dependency>` from Object value (token `JsonToken.FIELD_NAME`)
 at [Source: (BufferedInputStream); line: 3, column: 3]

    at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
    at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1767)
    at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1541)
    at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1488)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.handleNonArray(CollectionDeserializer.java:402)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:254)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:30)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1490)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:348)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
    at com.fasterxml.jackson.dataformat.xml.deser.XmlDeserializationContext.readRootValue(XmlDeserializationContext.java:104)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4917)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3897)
    at de.symate.maven.plugins.licenseaggregator.model.LicenseSummaryTest.xmlDeserializationWorks(LicenseSummaryTest.java:30)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)

Can someone help me?

The funny thing is, when I create a POJO of my model classes and serialize it using the exact same mapper, the above posted XML is the result. But trying to deserialize it does not work :(


Solution

  • As far as I understand https://github.com/FasterXML/jackson-dataformat-xml/issues/517, the problem lies within the annotation @JacksonXmlElementWrapper, which is not properly supported on constructor arguments (esspecially if you want to deserialize a collection structure with a wrapper element)

     <items>
        <item></item>
        <item></item>
    </items>
    

    So this is the solution I can work with. The default constructor that is required for Jackson to work properly can be set protected, so it does not get exposed and noone can abuse it to create shallow objects.

    @Getter
    @Setter
    @JacksonXmlRootElement(localName = "licenseSummary")
    @EqualsAndHashCode
    public class LicenseSummary {
        @NonNull
        @JacksonXmlProperty(localName = "dependency")
        @JacksonXmlElementWrapper(localName = "dependencies")
        List<Dependency> dependencies;
    
        public LicenseSummary(@NonNull List<Dependency> dependencies) {
            this.dependencies = dependencies;
        }
    
        /**
         * Protected no-args constructor for Jackson, because
         * @JacksonXmlElementWrapper does not work properly on constructor arguments.
         */
        @JsonCreator
        protected LicenseSummary() {}
    }
    
    @Getter
    @Setter
    @EqualsAndHashCode
    public class Dependency {
        @NonNull
        @JacksonXmlProperty(localName = "groupId")
        private String groupId;
    
        @JsonCreator
        public Dependency(@JacksonXmlProperty(localName = "groupId")
                @NonNull String groupId) {
            this.groupId = groupId;
        }
    }