Jackson: XML to Map with List deserialization

前端 未结 3 622
暖寄归人
暖寄归人 2020-12-15 05:52

Is there a way to deserialize the following xml into Map holding List of items using Jackson?


    12345678
    <         


        
相关标签:
3条回答
  • 2020-12-15 06:19

    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;
    
            }
    
        }
    
    }
    
    0 讨论(0)
  • 2020-12-15 06:29

    Created a custom deserializer by extending UntypedObjectDeserializer to do this job.

    0 讨论(0)
  • 2020-12-15 06:43

    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.

    0 讨论(0)
提交回复
热议问题