Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jackson xml lists deserialization recognized as duplicate keys

I'm trying to convert xml into json using jackson-2.5.1 and jackson-dataformat-xml-2.5.1
The xml structure is received from web server and unknown, therefore I can't have java class to represent the object, and I'm trying to convert directly into TreeNode using ObjectMapper.readTree.
My problem is jackson failing to parse lists. It is takes only the last item of the list.
code:

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);

The json result:

{"name":"john","list":{"item":"val3"}}  

If I enable failure on duplicate keys xmlMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY), exception is thrown:
com.fasterxml.jackson.databind.JsonMappingException: Duplicate field 'item' for ObjectNode: not allowed when FAIL_ON_READING_DUP_TREE_KEY enabled

Is there any feature which fixes this problem? Is there a way for me to write custom deserializer which in event of duplicate keys turn them into array?

like image 574
itaied Avatar asked May 12 '15 06:05

itaied


1 Answers

I use this approach:

  1. Plugin a serializer into XmlMapper using a guava multimap. This puts everything into lists.
  2. Write out the json using SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED. This unwrapps all lists with size==1.

Here is my code:

    @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);
        }
    }

It prints

{
  "list" : {
    "item" : [ "val1", "val3" ]
  },
  "name" : "john"
}

Altogether it is a variante of this approach, which comes out without guava multimap: https://github.com/DinoChiesa/deserialize-xml-arrays-jackson

Same approach is used here: Jackson: XML to Map with List deserialization

like image 193
jschnasse Avatar answered Sep 20 '22 12:09

jschnasse