【问题标题】:jaxb binding returning null value for nested map xml implementationjaxb 绑定为嵌套映射 xml 实现返回空值
【发布时间】:2020-07-02 20:25:40
【问题描述】:

正在解析的xml示例

<llsd>
    <map>
    <key>agents</key>
        <map>
        <key>0008c41e-3298-449c-8abd-4929a0eeae0e</key>
            <map>
            <key>display_name</key>
                <string>kettelynin</string>
            <key>display_name_expires</key>
                <date>2020-06-24T23:53:44.05Z</date>
            <key>display_name_next_update</key>
                <date>1970-01-01T00:00:00Z</date>
            <key>is_display_name_default</key>
                <boolean>1</boolean>
            <key>legacy_first_name</key>
                <string>kettelynin</string>
            <key>legacy_last_name</key>
                <string>Resident</string>
            <key>username</key>
                <string>username1</string>
            </map>
        <key>000bd88c-562f-423e-af63-55b9f0b17e10</key>
            <map>
            <key>display_name</key>
                <string>ϯ Mary Baker Pitbull Darkϯ  </string>
            <key>display_name_expires</key>
                <date>2020-06-25T00:24:25.63Z</date>
            <key>display_name_next_update</key>
                <date>1970-01-01T00:00:00Z</date>
            <key>is_display_name_default</key>
                <boolean>0</boolean>
            <key>legacy_first_name</key>
                <string>maryunasilva</string>
            <key>legacy_last_name</key>
                <string>Resident</string>
            <key>username</key>
                <string>username2</string>
            </map>
        </map>
    </map>
</llsd>

据我了解,该结构是一些带有字符串键和各种对象作为最终值的嵌套映射。所以,我有这门课。

import javax.xml.bind.annotation.XmlRootElement;
import java.util.Map;

@XmlRootElement
public class llsd {
    Map<String, Map<String, Object>> agents;

    llsd() {
    }

    public Map<String, Map<String, Object>> getAgents() {
        return agents;
    }
}

我正在运行下面的代码 sn-p 来测试它

JAXBContext newInstance = JAXBContext.newInstance(llsd.class);
Unmarshaller unmarshall = newInstance.createUnmarshaller();
llsd newParsed = (llsd) unmarshall.unmarshal(new File("C:/path/to/my.xml"));

我在运行它时没有收到任何错误,但是当我运行调试器时,我可以看到代理的 newParsed 值为 null。显然我的绑定设置错误,但我不知道为什么。

【问题讨论】:

    标签: java xml-parsing jaxb


    【解决方案1】:

    您的 XML 结构的嵌套级别比 Map&lt;String, Map&lt;String, Object&gt;&gt; 所考虑的要多。您可以查看其他与 JAXB 相关的问题中建议的一些方法,例如一般方法的 herehere

    如果您不熟悉 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——一个用于&lt;key&gt; 标签集,一个用于相关&lt;map&gt; 标签集:

    <key>0008c41e-3298-449c-8abd-4929a0eeae0e</key>
    <map>...</map>
    

    相关的 xPath 选择器是 /llsd/map/map/key/llsd/map/map/map

    这里的假设是每个标签的数量总是相同的(您的示例中每个标签都有两个)。您可以添加一些防御性代码来检查这一点。

    buildAgent() 方法为一个代理处理每组数据。它使用addValue() 中的switch 语句调用Agent bean 中的相关设置器。

    您的示例数据的最终结果是 List&lt;Agent&gt; 包含 2 个对象。

    【讨论】:

      猜你喜欢
      • 2013-10-27
      • 2019-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-05
      • 2015-04-05
      • 2022-08-13
      • 1970-01-01
      相关资源
      最近更新 更多