jackson xml 列出了被识别为重复键的反序列化
Posted
技术标签:
【中文标题】jackson xml 列出了被识别为重复键的反序列化【英文标题】:jackson xml lists deserialization recognized as duplicate keys 【发布时间】:2015-07-22 21:07:43 【问题描述】:我正在尝试使用 jackson-2.5.1
和 jackson-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 列出了被识别为重复键的反序列化的主要内容,如果未能解决你的问题,请参考以下文章