jackson xml 列出了被识别为重复键的反序列化

Posted

技术标签:

【中文标题】jackson xml 列出了被识别为重复键的反序列化【英文标题】:jackson xml lists deserialization recognized as duplicate keys 【发布时间】:2015-07-22 21:07:43 【问题描述】:

我正在尝试使用 jackson-2.5.1jackson-dataformat-xml-2.5.1 将 xml 转换为 json xml 结构是从 web 服务器接收的并且未知,因此我不能使用 java 类来表示对象,我正在尝试使用 ObjectMapper.readTree 直接转换为 TreeNode。 我的问题是杰克逊无法解析列表。它只需要列表的最后一项。 代码:

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

有什么功能可以解决这个问题吗?有没有办法让我编写自定义反序列化器,在重复键的情况下将它们变成数组?

【问题讨论】:

你可能不走运github.com/FasterXML/jackson-dataformat-xml/issues/… 现在好像支持了:github.com/FasterXML/jackson-dataformat-xml/issues/… 【参考方案1】:

您可以捕获该异常并执行以下操作:

List<MyClass> myObjects = mapper.readValue(input, new TypeReference<List<MyClass>>());

(从这里得到它How to use Jackson to deserialise an array of objects)

这是一种骇人听闻的方法,您必须弄清楚如何从那里恢复。

【讨论】:

这不是我的用例。首先我没有java类。第二件事,列表嵌入在 xml 中的某处,它不是根目录下的对象列表。【参考方案2】:

我遇到了同样的问题,并决定使用简单的 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;
            

【讨论】:

【参考方案3】:

Underscore-java 库支持此 XML。

String xml = "<root><name>john</name><list><item>val1</item>val2<item>val3</item></list></root>";
String json = U.xmlToJson(xml);
System.out.println(json);

输出 JSON:


  "root": 
    "name": "john",
    "list": 
      "item": [
        "val1",
        
          "#item": 
            "#text": "val2"
          
        ,
        "val3"
      ]
    
  ,
  "#omit-xml-declaration": "yes"

【讨论】:

【参考方案4】:

我使用这种方法:

    使用guava multimap 将serializer 插入XmlMapper。这会将所有内容都放入列表中。 使用SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED 写出json。这将使用 size==1 解开所有列表。

这是我的代码:

    @Test
    public void xmlToJson() 
        String xml = "<root><name>john</name><list><item>val1</item>val2<item>val3</item></list></root>";
        Map<String, Object> jsonResult = readXmlToMap(xml);
        String jsonString = toString(jsonResult);
        System.out.println(jsonString);
    

    private Map<String, Object> readXmlToMap(String xml) 
        try 
            ObjectMapper xmlMapper = new XmlMapper();
            xmlMapper.registerModule(new SimpleModule().addDeserializer(Object.class, new UntypedObjectDeserializer() 
                @SuppressWarnings( "unchecked", "rawtypes" )
                @Override
                protected Map<String, Object> mapObject(JsonParser jp, DeserializationContext ctxt) throws IOException 
                    JsonToken t = jp.getCurrentToken();

                    Multimap<String, Object> result = ArrayListMultimap.create();
                    if (t == JsonToken.START_OBJECT) 
                        t = jp.nextToken();
                    
                    if (t == JsonToken.END_OBJECT) 
                        return (Map) result.asMap();
                    
                    do 
                        String fieldName = jp.getCurrentName();
                        jp.nextToken();
                        result.put(fieldName, deserialize(jp, ctxt));
                     while (jp.nextToken() != JsonToken.END_OBJECT);

                    return (Map) result.asMap();
                
            ));
            return (Map) xmlMapper.readValue(xml, Object.class);
         catch (Exception e) 
            throw new RuntimeException(e);
        
    

    static public String toString(Object obj) 
        try 
            ObjectMapper jsonMapper = new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true)
                    .configure(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED, true);
            StringWriter w = new StringWriter();
            jsonMapper.writeValue(w, obj);
            return w.toString();
         catch (Exception e) 
            throw new RuntimeException(e);
        
    

打印出来


  "list" : 
    "item" : [ "val1", "val3" ]
  ,
  "name" : "john"

总之它是这种方法的一个变种,它在没有 guava 多图的情况下出现: https://github.com/DinoChiesa/deserialize-xml-arrays-jackson

这里使用相同的方法: Jackson: XML to Map with List deserialization

【讨论】:

以上是关于jackson xml 列出了被识别为重复键的反序列化的主要内容,如果未能解决你的问题,请参考以下文章

我收到此错误“'npx' 未被识别为内部或外部命令,” [重复]

枚举的反序列化不起作用 - Jackson [重复]

Python argv“假,假”被识别为真[重复]

python不被识别为内部或外部命令[重复]

R中的列未被识别为变量[重复]

c# datetime.parseexact 字符串未被识别为有效的日期时间 [重复]