您的 XML 结构的嵌套级别比 Map<String, Map<String, Object>> 所考虑的要多。您可以查看其他与 JAXB 相关的问题中建议的一些方法,例如一般方法的 here 和 here。
如果您不熟悉 xjc,它也可以帮助您构建 JAXB 所需的对象 - 请参阅注释 here。
但是,对于诸如您问题中的特定结构之类的结构,我会考虑另一种(非 JAXB)方法。原因之一是因为你的数据有有序的标签对,比如:
<key>display_name_expires</key>
<date>2020-06-25T00:24:25.63Z</date>
您在一个标记中具有字段名称,然后在以下标记中具有相关的数据类型和值。我不知道在 JAXB 中有什么干净的方法来处理这个问题(当然,可能有办法)。
我的替代方法是使用 Java 的 xPath 类以有针对性的方式解析 XML,并构建 Agent 数据 bean 的列表。这是一个手动过程,但相当简单。
Agent豆豆:
import java.util.UUID;
import java.time.Instant;
public class Agent {
private UUID id;
private String displayName;
private Instant displayNameExpires;
private Instant displayNameNextUpdate;
private boolean isDisplayNameDefault;
private String legacyFirstName;
private String legacyLastName;
private String userName;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public Instant getDisplayNameExpires() {
return displayNameExpires;
}
public void setDisplayNameExpires(String displayNameExpires) {
this.displayNameExpires = Instant.parse(displayNameExpires);
}
public Instant getDisplayNameNextUpdate() {
return displayNameNextUpdate;
}
public void setDisplayNameNextUpdate(String displayNameNextUpdate) {
this.displayNameNextUpdate = Instant.parse(displayNameNextUpdate);
}
public boolean getIsDisplayNameDefault() {
return isDisplayNameDefault;
}
public void setIsDisplayNameDefault(String isDefault) {
this.isDisplayNameDefault = isDefault.equals("1") ? Boolean.TRUE
: Boolean.FALSE;
}
public String getLegacyFirstName() {
return legacyFirstName;
}
public void setLegacyFirstName(String legacyFirstName) {
this.legacyFirstName = legacyFirstName;
}
public String getLegacyLastName() {
return legacyLastName;
}
public void setLegacyLastName(String legacyLastName) {
this.legacyLastName = legacyLastName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
在上面的类中,唯一需要注意的是在相关的 getter 中包含数据转换,从 String 到其他类型,如果需要(即时和布尔值)。
XML 处理器:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.xml.sax.SAXException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
public class AgentReader {
private final List<Agent> agents = new ArrayList();
public void processAgents() throws FileNotFoundException, ParserConfigurationException,
SAXException, IOException, XPathExpressionException {
FileInputStream fis = new FileInputStream(new File("/path/to/llsd.xml"));
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document xmlDocument = builder.parse(fis);
xmlDocument.normalizeDocument();
XPathExpression idPath = XPathFactory.newInstance().newXPath()
.compile("/llsd/map/map/key");
XPathExpression attrMapPath = XPathFactory.newInstance().newXPath()
.compile("/llsd/map/map/map");
// This is relative to the attrMapPath defined above:
XPathExpression attrsPath = XPathFactory.newInstance().newXPath()
.compile("./*");
NodeList idNodes = (NodeList) idPath.evaluate(xmlDocument, XPathConstants.NODESET);
NodeList attrLists = (NodeList) attrMapPath.evaluate(xmlDocument, XPathConstants.NODESET);
for (int i = 0; i < idNodes.getLength(); i++) {
Node idNode = idNodes.item(i);
Node attrList = attrLists.item(i);
agents.add(buildAgent(idNode, attrList, attrsPath));
}
}
private Agent buildAgent(Node idNode, Node attrList, XPathExpression attrsPath)
throws XPathExpressionException {
Agent agent = new Agent();
String id = idNode.getTextContent();
NodeList attrNodes = (NodeList) attrsPath.evaluate(attrList, XPathConstants.NODESET);
String fieldName = null;
for (int i = 0; i < attrNodes.getLength(); i++) {
// The data comes in pairs of tags - a field name and a related value:
if (i % 2 == 0) {
fieldName = attrNodes.item(i).getTextContent();
} else {
String value = attrNodes.item(i).getTextContent();
agent = addValue(fieldName, value, agent);
}
}
return agent;
}
private Agent addValue(String fieldName, String value, Agent agent) {
switch (fieldName) {
case "display_name":
agent.setDisplayName(value);
break;
case "display_name_expires":
agent.setDisplayNameExpires(value);
break;
case "display_name_next_update":
agent.setDisplayNameNextUpdate(value);
break;
case "is_display_name_default":
agent.setIsDisplayNameDefault(value);
break;
case "legacy_first_name":
agent.setLegacyFirstName(value);
break;
case "legacy_last_name":
agent.setLegacyLastName(value);
break;
case "username":
agent.setUserName(value);
break;
}
return agent;
}
}
在processAgents() 中,xPaths 允许我们忽略外部嵌套标签,直接进入数据。
因为这些标签处于同一级别,我们并行使用 2 个 xPath——一个用于<key> 标签集,一个用于相关<map> 标签集:
<key>0008c41e-3298-449c-8abd-4929a0eeae0e</key>
<map>...</map>
相关的 xPath 选择器是 /llsd/map/map/key 和 /llsd/map/map/map。
这里的假设是每个标签的数量总是相同的(您的示例中每个标签都有两个)。您可以添加一些防御性代码来检查这一点。
buildAgent() 方法为一个代理处理每组数据。它使用addValue() 中的switch 语句调用Agent bean 中的相关设置器。
您的示例数据的最终结果是 List<Agent> 包含 2 个对象。