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
Your problem is a little tricky because you need to solve a few issues. Let's start to solve them one by one.
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.
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:
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
date
property is annotated with @JsonFormat
to specify the
date-time format to be usedelements
is annotated with @JacksonXmlElementWrapper(useWrapping = false)
to tell the parser to not expect an extra wrapper around the sequence of <element>
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With