问题
I want to build a parser to parse an XML
file in Java
.
As you can see in my code below I am using a LinkedHashMap
to access the desired values First
, Middle
and Last
.
My problem is that I have NameList
tags containing multiple Person
tags and the LinkedHashMap
so far gives me only the last Person
, in my example (output):
given: Ghi
family: Tom
given: Jkl
family: Mary
How can I access the other two (Karl Abc, Thomas Def) using my approach with LinkedHashMap
?
This is my XML
file:
<Sources>
<Source>
<Year>2019</Year>
</Source>
<Source>
<Title>Blablabla</Title>
<Author>
<BookAuthor>
<NameList>
<Person>
<Last>Karl</Last>
<First>Abc</First>
</Person>
<Person>
<Last>Thomas</Last>
<First>Def</First>
</Person>
<Person>
<Last>Tom</Last>
<First>Ghi</First>
</Person>
</NameList>
</BookAuthor>
</Author>
</Source>
<Source>
<Author>
<Editor>
<NameList>
<Person>
<Last>Mary</Last>
<First>Jkl</First>
</Person>
</NameList>
</Editor>
</Author>
</Source>
This is my code:
private static void XmlFileParser() throws IOException {
InputStream xmlFile = Publication.class.getClassLoader().getResourceAsStream("test.xml");
ObjectMapper mapper = new XmlMapper();
// Configure
mapper
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
Object[] deserializedData = mapper.readValue(xmlFile, Object[].class);
for (Object element : deserializedData) {
if (element instanceof LinkedHashMap) {
LinkedHashMap<String, Object> el = (LinkedHashMap<String, Object>) element;
if ((el.get("Author")) == null) {
continue;
} else {
// Last -> family
// First, Middle -> given
if (((LinkedHashMap) el.get("Author")).get("Author") instanceof LinkedHashMap && ((((LinkedHashMap) ((LinkedHashMap) el
.get("Author")).get("Author")).get("NameList")) != null)) {
Object first = ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) el.get(
"Author")).get("Author")).get("NameList")).get("Person")).get("First");
Object middle = ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) el.get(
"Author")).get("Author")).get("NameList")).get("Person")).get("Middle");
if (first != null || middle != null) {
System.out.println("given: " + evaluateGiven(first, middle));
}
Object family = ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) el.get(
"Author")).get("Author")).get("NameList")).get("Person")).get("Last");
System.out.println("family: " + family);
} else if (((LinkedHashMap) el.get("Author")).get("Editor") instanceof LinkedHashMap && ((((LinkedHashMap) ((LinkedHashMap) el
.get("Author")).get("Editor")).get("NameList")) != null)) {
Object first = ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) el.get(
"Author")).get("Editor")).get("NameList")).get("Person")).get("First");
Object middle = ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) el.get(
"Author")).get("Editor")).get("NameList")).get("Person")).get("Middle");
if (first != null || middle != null) {
System.out.println("given: " + evaluateGiven(first, middle));
}
Object family = ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) ((LinkedHashMap) el.get(
"Author")).get("Editor")).get("NameList")).get("Person")).get("Last");
System.out.println("family: " + family);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
My helper class:
private static String evaluateGiven(Object first, Object middle) {
if (first == null) {
first = "";
} else if (middle == null) {
middle = "";
}
return first.toString() + " " + middle.toString();
}
Since my code is very blown up, do you know how to make it smaller?
I would be very happy about some help.
Thank you!
回答1:
Generally lists are not handled easily and when we do not use POJO
structure we need to write custom deserialiser which will use Streaming API to read inner list objects. Below you can find simple POJO
model with custom deserialiser for Author
class:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
public class XmlMapperApp {
public static void main(String[] args) throws Exception {
File xmlFile = new File("./resource/test.xml").getAbsoluteFile();
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.setDefaultUseWrapper(false);
xmlMapper.setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);
Source[] sources = xmlMapper.readValue(xmlFile, Source[].class);
Stream.of(sources)
.filter(s -> Objects.nonNull(s.getAuthor()))
.map(s -> s.getAuthor().getPersons())
.filter(a -> !a.isEmpty())
.forEach(System.out::println);
}
}
class AuthorJsonDeserializer extends JsonDeserializer<Author> {
@Override
public Author deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
p.nextToken(); // Skip START_OBJECT
Author author = new Author();
author.setType(p.getText());
author.setPersons(new ArrayList<>());
JsonToken token;
while ((token = p.currentToken()) != JsonToken.END_OBJECT) {
if (token == JsonToken.FIELD_NAME) {
String name = p.getText();
if ("Person".equals(name)) {
p.nextToken();
author.getPersons().add(p.readValueAs(Person.class));
}
}
p.nextToken();
}
p.nextToken(); // Skip END_OBJECT
p.nextToken(); // Skip END_OBJECT
return author;
}
}
class Source {
private int year;
private String title;
private Author author;
// getters, setters, toString
}
@JsonDeserialize(using = AuthorJsonDeserializer.class)
class Author {
private String type;
private List<Person> persons;
// getters, setters, toString
}
class Person {
private String last;
private String first;
// getters, setters, toString
}
Above code prints:
[Person{last='Karl', first='Abc'}, Person{last='Thomas', first='Def'}, Person{last='Tom', first='Ghi'}]
[Person{last='Mary', first='Jkl'}]
You can print whole Source
instance: Stream.of(sources).forEach(System.out::println);
and you should see:
Source{year=2019, title='null', author=null}
Source{year=0, title='Blablabla', author=Author{type='BookAuthor', persons=[Person{last='Karl', first='Abc'}, Person{last='Thomas', first='Def'}, Person{last='Tom', first='Ghi'}]}}
Source{year=0, title='null', author=Author{type='Editor', persons=[Person{last='Mary', first='Jkl'}]}}
See also: how to ignore outer wrappers when parsing?
来源:https://stackoverflow.com/questions/57297149/how-to-parse-xml-file-containing-multiple-tags-using-linkedhashmap