javajacksonpojojackson-databindjackson-dataformat-xml

Deserialize flat array in XML by Jackson to List of Pojo


Can you help me to parse the following XML file?

<?xml version="1.0" encoding="UTF-8"?>
<dataset xmlns="http:/foo.com">
   <date>2017-10-25T09:13:54+02:00</date>
   <element>
      <id>1</id>
      <name>Stuart</name>
      <age>34</age>
      <regdate><date>2017-10-25T09:13:54+02:00</date></regdate>
   </element>
   <element>
      <id>2</id>
      <name>Lora</name>
      <age>12</age>
      <regdate><date>2017-10-25T09:13:54+02:00</date></regdate>
   </element>
   <element>
      <id>3</id>
      <name>Ben</name>
      <age>50</age>
      <regdate><date>2017-10-25T09:13:54+02:00</date></regdate>
   </element>
</dataset >

I tried to create POJO like this:

@Getter
@Setter
@JacksonXmlRootElement(localName = "element")
public class ElementXML {
    @JacksonXmlProperty(localName = "id")
    private Long id;    
    @JacksonXmlProperty(localName = "name")
    private String name;
    @JacksonXmlProperty(localName = "age")
    private Long age;
    @JacksonXmlProperty(localName = "regdate")
    private LocalDateTime regdate;
}

and parsing mechanism that I've used is here:

    XMLInputFactory f = XMLInputFactory.newFactory();
    File inputFile = new File("some path");
    XMLStreamReader sr = f.createXMLStreamReader(new FileInputStream(inputFile));


    ObjectMapper xmlMapper = new XmlMapper();
    

There I am stuck because I don't know how to parse just element tags into a List of my created POJO ElementXML. Do you have any idea how to solve it?

EDIT Trace after edited parsing by answers:

com.fasterxml.jackson.databind.JsonMappingException: Expected END_ELEMENT, got event of type 1 (through reference chain: com.xml.Dataset["element"]->java.lang.Object[][1])

    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394)
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:365)
    at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:206)
    at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:21)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer.deserialize(WrapperHandlingDeserializer.java:113)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2902)
    at com.xml.data.ParseXmlTest.test(ParseXmlTest.java:62)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:67)
Caused by: java.io.IOException: Expected END_ELEMENT, got event of type 1
    at com.fasterxml.jackson.dataformat.xml.deser.XmlTokenStream.skipEndElement(XmlTokenStream.java:190)
    at com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser.nextToken(FromXmlParser.java:584)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:283)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:195)
    ... 43 more

Solution

  • Your problem is a little tricky because you need to solve a few issues. Let's start to solve them one by one.

    1. The model does not fit the XML payload.

    First of all, you need to create a model which fits your payload. It does not depend on the format because for JSON and XML it will be almost the same. To do that I propose always starting from the serialization process. It is much easier to build the model in Java and try to serialize it. In case it does not look the same as expected you need to update the model. You iterate these steps: update and serialise until you will find the valid model. After that, you can deserialize the given payload without any problems.

    2. Jackson annotations.

    Even so Jackson's annotations are great do not use them without a reason. If the POJO property is the same as the node name in XML you do not need to add the JacksonXmlProperty annotation. You must add it when names in POJO and payload are different. In other cases, this is overcomplicating the POJO structure. We should keep it as simple as possible. You need to use one tricky annotation: JacksonXmlElementWrapper. It is used when we have collections of nodes but they are unwrapped.

    After these two simple paragraphs let's deserialize your case. We need to extend your POJO structure and it should look like the below:

    class Dataset {
    
        private LocalDateTime date;
    
        @JacksonXmlProperty(localName = "element")
        @JacksonXmlElementWrapper(useWrapping = false)
        private List<Element> elements;
    
        public LocalDateTime getDate() {
            return date;
        }
    
        public void setDate(LocalDateTime date) {
            this.date = date;
        }
    
        public List<Element> getElements() {
            return elements;
        }
    
        public void setElements(List<Element> element) {
            this.elements = element;
        }
    
        @Override
        public String toString() {
            return "Dataset{" +
                    "date=" + date +
                    ", element=" + elements +
                    '}';
        }
    }
    
    class Element {
    
        private Long id;
        private String name;
        private Long age;
        private RegDate regdate;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Long getAge() {
            return age;
        }
    
        public void setAge(Long age) {
            this.age = age;
        }
    
        public RegDate getRegdate() {
            return regdate;
        }
    
        public void setRegdate(RegDate regdate) {
            this.regdate = regdate;
        }
    
        @Override
        public String toString() {
            return "ElementXML{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    ", regdate=" + regdate.getDate() +
                    '}';
        }
    }
    
    class RegDate {
    
        private LocalDateTime date;
    
        public RegDate() {
            this(null);
        }
    
        public RegDate(LocalDateTime date) {
            this.date = date;
        }
    
        public LocalDateTime getDate() {
            return date;
        }
    
        public void setDate(LocalDateTime date) {
            this.date = date;
        }
    
        @Override
        public String toString() {
            return "RegDate{" +
                    "date=" + date +
                    '}';
        }
    }
    

    An example usage:

    import com.fasterxml.jackson.databind.SerializationFeature;
    import com.fasterxml.jackson.dataformat.xml.XmlMapper;
    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
    import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
    import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
    
    import java.io.File;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.List;
    
    public class XmlMapperApp {
    
        public static void main(String[] args) throws Exception {
            File jsonFile = new File("./resource/test.xml").getAbsoluteFile();
    
            JavaTimeModule module = new JavaTimeModule();
            module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ISO_DATE_TIME));
    
            XmlMapper xmlMapper = new XmlMapper();
            xmlMapper.registerModule(module);
            xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
    
            Dataset dataset = xmlMapper.readValue(jsonFile, Dataset.class);
            dataset.getElements().forEach(System.out::println);
        }
    }
    

    Above code prints:

    ElementXML{id=1, name='Stuart', age=34, regdate=2017-10-25T09:13:54}
    ElementXML{id=2, name='Lora', age=12, regdate=2017-10-25T09:13:54}
    ElementXML{id=3, name='Ben', age=50, regdate=2017-10-25T09:13:54}
    

    Two extra comments to above code. When you are working with java.time.* classes and Jackson is good to start from registering JavaTimeModule which comes from jackson-datatype-jsr310 module. Since we are using it we can instruct it to use ISO_DATE_TIME formatting for LocalDateTime classes. In other answers you can find example where JsonFormat annotation is used. It is also good solution but when all dates have the same format much easier to define it ones.

    For more information, read: