问题
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