Jackson: XML to Map with List deserialization
This is a known jackson-dataformat-xml
bug filed under issue 205. In a nutshell, duplicated elements in the XML get swallowed by the current UntypedObjectDeserializer
implementation. Fortunately, the author (João Paulo Varandas) of the report also provided a temporary fix in the form a custom UntypedObjectDeserializer
implementation. Below I share my interpretation of the fix:
import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.core.JsonToken;import com.fasterxml.jackson.databind.DeserializationContext;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer;import com.fasterxml.jackson.databind.module.SimpleModule;import com.fasterxml.jackson.dataformat.xml.XmlMapper;import javax.annotation.Nullable;import java.io.IOException;import java.util.*;public enum JacksonDataformatXmlIssue205Fix {; public static void main(String[] args) throws IOException { String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<items>\n" + " <item><id>1</id></item>\n" + " <item><id>2</id></item>\n" + " <item><id>3</id></item>\n" + "</items>"; SimpleModule module = new SimpleModule().addDeserializer(Object.class, Issue205FixedUntypedObjectDeserializer.getInstance()); XmlMapper xmlMapper = (XmlMapper) new XmlMapper().registerModule(module); Object object = xmlMapper.readValue(xml, Object.class); System.out.println(object); // {item=[{id=1}, {id=2}, {id=3}]} } @SuppressWarnings({ "deprecation", "serial" }) public static class Issue205FixedUntypedObjectDeserializer extends UntypedObjectDeserializer { private static final Issue205FixedUntypedObjectDeserializer INSTANCE = new Issue205FixedUntypedObjectDeserializer(); private Issue205FixedUntypedObjectDeserializer() {} public static Issue205FixedUntypedObjectDeserializer getInstance() { return INSTANCE; } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) protected Object mapObject(JsonParser parser, DeserializationContext context) throws IOException { // Read the first key. @Nullable String firstKey; JsonToken token = parser.getCurrentToken(); if (token == JsonToken.START_OBJECT) { firstKey = parser.nextFieldName(); } else if (token == JsonToken.FIELD_NAME) { firstKey = parser.getCurrentName(); } else { if (token != JsonToken.END_OBJECT) { throw context.mappingException(handledType(), parser.getCurrentToken()); } return Collections.emptyMap(); } // Populate entries. Map<String, Object> valueByKey = new LinkedHashMap<>(); String nextKey = firstKey; do { // Read the next value. parser.nextToken(); Object nextValue = deserialize(parser, context); // Key conflict? Combine existing and current entries into a list. if (valueByKey.containsKey(nextKey)) { Object existingValue = valueByKey.get(nextKey); if (existingValue instanceof List) { List<Object> values = (List<Object>) existingValue; values.add(nextValue); } else { List<Object> values = new ArrayList<>(); values.add(existingValue); values.add(nextValue); valueByKey.put(nextKey, values); } } // New key? Put into the map. else { valueByKey.put(nextKey, nextValue); } } while ((nextKey = parser.nextFieldName()) != null); // Ship back the collected entries. return valueByKey; } }}
The other answers don't work if you have to use readTree()
and JsonNode
. I know it's an ugly solution but at least you don't need to paste someone's gist in your project.
Add org.json to your project dependencies.
And then do the following:
import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;import org.json.JSONObject;import org.json.XML;...private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();... JSONObject soapDatainJsonObject = XML.toJSONObject(data); return OBJECT_MAPPER.readTree(soapDatainJsonObject.toString());
The conversion goes as follows:
XML -> JSONObject (using org.json) -> string -> JsonNode (using readTree)
Of course, toJSONObject handles duplicates without any problems, I suggest to avoid using Jackson and readTree()
if you can.
Created a custom deserializer by extending UntypedObjectDeserializer to do this job.