我正在尝试使用jackson-2.5.1jackson-dataformat-xml-2.5.1将xml转换为json
xml结构是从Web服务器接收的,并且未知,因此我无法使用Java类来表示该对象,因此我尝试使用TreeNode直接转换为ObjectMapper.readTree
我的问题是 jackson 无法解析列表。它仅占用列表的最后一项。
码:

String xml = "<root><name>john</name><list><item>val1</item>val2<item>val3</item></list></root>";
XmlMapper xmlMapper = new XmlMapper();
JsonNode jsonResult = xmlMapper.readTree(xml);

json结果:
{"name":"john","list":{"item":"val3"}}

如果我对重复键xmlMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY)启用失败,则会引发异常:com.fasterxml.jackson.databind.JsonMappingException: Duplicate field 'item' for ObjectNode: not allowed when FAIL_ON_READING_DUP_TREE_KEY enabled
有解决此问题的功能吗?我有没有办法编写自定义反序列化器,一旦键重复,它们会将它们转换为数组?

最佳答案

我遇到了同样的问题,并决定使用简单的DOM进行滚动。主要问题是XML确实不像JSon那样适合于Map-List-Object类型映射。但是,根据某些假设,仍然有可能:

  • 文本作为单个字符串或列表存储在空键中。
  • 空元素,即使用空地图建模。

  • 这堂课是希望对别人有所帮助:
    public class DeXML {
    
        public DeXML() {}
    
        public Map<String, Object> toMap(InputStream is) {
            return toMap(new InputSource(is));
        }
    
        public Map<String, Object> toMap(String xml) {
            return toMap(new InputSource(new StringReader(xml)));
        }
    
        private Map<String, Object> toMap(InputSource input) {
            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document document = builder.parse(input);
                document.getDocumentElement().normalize();
                Element root = document.getDocumentElement();
                return visitChildNode(root);
            } catch (ParserConfigurationException | SAXException | IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        // Check if node type is TEXT or CDATA and contains actual text (i.e. ignore
        // white space).
        private boolean isText(Node node) {
            return ((node.getNodeType() == Element.TEXT_NODE || node.getNodeType() == Element.CDATA_SECTION_NODE)
                    && node.getNodeValue() != null && !node.getNodeValue().trim().isEmpty());
        }
    
        private Map<String, Object> visitChildNode(Node node) {
            Map<String, Object> map = new HashMap<>();
    
            // Add the plain attributes to the map - fortunately, no duplicate attributes are allowed.
            if (node.hasAttributes()) {
                NamedNodeMap nodeMap = node.getAttributes();
                for (int j = 0; j < nodeMap.getLength(); j++) {
                    Node attribute = nodeMap.item(j);
                    map.put(attribute.getNodeName(), attribute.getNodeValue());
                }
            }
    
            NodeList nodeList = node.getChildNodes();
    
            // Any text children to add to the map?
            List<Object> list = new ArrayList<>();
            for (int i = 0; i < node.getChildNodes().getLength(); i++) {
                Node child = node.getChildNodes().item(i);
                if (isText(child)) {
                    list.add(child.getNodeValue());
                }
            }
            if (!list.isEmpty()) {
                if (list.size() > 1) {
                    map.put(null, list);
                } else {
                    map.put(null, list.get(0));
                }
            }
    
            // Process the element children.
            for (int i = 0; i < node.getChildNodes().getLength(); i++) {
    
                // Ignore anything but element nodes.
                Node child = nodeList.item(i);
                if (child.getNodeType() != Element.ELEMENT_NODE) {
                    continue;
                }
    
                // Get the subtree.
                Map<String, Object> childsMap = visitChildNode(child);
    
                // Now, check if this is key already exists in the map. If it does
                // and is not a List yet (if it is already a List, simply add the
                // new structure to it), create a new List, add it to the map and
                // put both elements in it.
                if (map.containsKey(child.getNodeName())) {
                    Object value = map.get(child.getNodeName());
                    List<Object> multiple = null;
                    if (value instanceof List) {
                        multiple = (List<Object>)value;
                    } else {
                        map.put(child.getNodeName(), multiple = new ArrayList<>());
                        multiple.add(value);
                    }
                    multiple.add(childsMap);
                } else {
                    map.put(child.getNodeName(), childsMap);
                }
            }
            return map;
        }
    }
    

    08-04 17:05