Jackson: XML to Map with List deserialization Jackson: XML to Map with List deserialization xml xml

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.