我正在尝试使用jackson-2.5.1
和jackson-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;
}
}