简体   繁体   中英

Deserialize flat array in XML by Jackson to List of Pojo

can you help me to parse 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 what 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 stuck because I don't know how to parse just element tags into List of my created pojo ElementXML. Do you have idea how to solve it? Thank you in advice.

Edit Trace after eddited 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

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

1. Model does not fit to XML payload.

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

2. Jackson annotations.

Even so Jackson 's annotations are great do not use them without a reason. If POJO property is the same as node name in XML you do not need to add JacksonXmlProperty annotation. You must to add it when names in POJO and payload are different. In other cases this is overcomplicating of 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 lest deserialise your case. We need to extend your POJO structure and it should look like 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 +
                '}';
    }
}

And 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:

It is not possible to model the given XML content by only one POJO class. You will need several POJO classes for modeling your XML contents properly.

For modeling the <dataset> root element you need a class, let's call it Dataset .

@Getter
@Setter
@JacksonXmlRootElement(localName = "dataset")
public class Dataset {

    @JacksonXmlProperty(localName = "date")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
    private LocalDateTime date;

    @JacksonXmlProperty(localName = "element")
    @JacksonXmlElementWrapper(useWrapping = false)
    private List<ElementXML> elements;
}

Notice in the code above

Next, you need a class for modeling the XML content between <element>...</element> , very much like your ElementXML class.

@Getter
@Setter
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 RegDate regdate;
}

Notice in the code above, that you need to model the regdate property more sophisticated. Because the XML content does not look like <regdate>2017-10-25T09:13:54+02:00</regdate> you cannot simply declare it as LocalDate regdate . Instead the XML looks like <regdate><date>2017-10-25T09:13:54+02:00</date></regdate> . Therefore you need to model it with yet another class (let's call it RegDate )

And finally, here is the RegDate class for modeling the XML content between <regdate>...</regdate> .

@Getter
@Setter
public class RegDate {

    @JacksonXmlProperty(localName = "date")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
    private LocalDateTime date;
}

Notice again the use of @JsonFormat to specify the date-time format.

Using the classes above you can parse XML like this

    File inputFile = new File("some path");
    ObjectMapper xmlMapper = new XmlMapper();
    xmlMapper.registerModule(new JavaTimeModule());
    Dataset dataset = xmlMapper.readValue(inputFile, Dataset.class);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM