Cannot deserialize (convert) unwrapped list when it's second in class using Jackson XmlWrapper

前端 未结 2 964
面向向阳花
面向向阳花 2020-12-21 15:21

I am trying to use XmlMapper from Jackson to deserialize some simple xml files containing unwrapped lists.

My code:

package zm.study.xmlserialize.jac         


        
2条回答
  •  醉酒成梦
    2020-12-21 16:14

    I am not sure it is possible to do that in this way. readTree method returns object which extends JsonNode, in that case it will be ObjectNode. ObjectNode does not accept two properties with the same name, and finally after deserialisation it represents:

    {"c":"c","as":"a2"}
    

    After that you want to convert this node to A POJO class. Default deserialiser for List expects START_ARRAY token not String. You can make it work by implementing custom converter which extends StdConverter but list will be trimmed to one element. In that case, I think, you have to use readValue method because you need to instruct Jackson that as elements are unwrapped array.

    EDIT
    After your comment I realised we can trick XmlMapper to use whatever we want. It is pretty obvious that Jackson uses JsonNodeDeserializer to deserialise JsonNode-s. So, all we need to do is to find a place where we can inject our code. Fortunately there is a method _handleDuplicateField which handles our case. By default it throws exception if FAIL_ON_READING_DUP_TREE_KEY flag is enabled:

    protected void _handleDuplicateField(JsonParser p, DeserializationContext ctxt,
            JsonNodeFactory nodeFactory,
            String fieldName, ObjectNode objectNode,
            JsonNode oldValue, JsonNode newValue)
        throws JsonProcessingException
    {
        // [databind#237]: Report an error if asked to do so:
        if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY)) {
            ctxt.reportInputMismatch(JsonNode.class,
                    "Duplicate field '%s' for ObjectNode: not allowed when FAIL_ON_READING_DUP_TREE_KEY enabled",
                    fieldName);
        }
    }
    

    So, let's use this fact and extend this class:

    class MergeDuplicateFieldsJsonNodeDeserializer extends JsonNodeDeserializer {
        @Override
        protected void _handleDuplicateField(JsonParser p, DeserializationContext ctxt, 
                                             JsonNodeFactory nodeFactory, String fieldName, ObjectNode objectNode, 
                                             JsonNode oldValue, JsonNode newValue) throws JsonProcessingException {
            super._handleDuplicateField(p, ctxt, nodeFactory, fieldName, objectNode, oldValue, newValue);
    
            ArrayNode array;
            if (oldValue instanceof ArrayNode) {
                // Merge 3-rd, 4-th, ..., n-th element to already existed array
                array = (ArrayNode) oldValue;
                array.add(newValue);
            } else {
                // Merge first two elements
                array = nodeFactory.arrayNode();
                array.add(oldValue);
                array.add(newValue);
            }
            objectNode.set(fieldName, array);
        }
    }
    

    Now, we need to register this deserialiser. Whole test could look like below:

    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    import com.fasterxml.jackson.databind.node.ArrayNode;
    import com.fasterxml.jackson.databind.node.JsonNodeFactory;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import com.fasterxml.jackson.dataformat.xml.XmlMapper;
    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
    
    import java.util.Arrays;
    import java.util.List;
    
    public class XmlMapperApp {
    
        public static void main(String[] args) throws Exception {
            A a = new A();
            a.c = "String";
            a.as = Arrays.asList("1", "2", "tom", "Nick");
    
            SimpleModule mergeDuplicatesModule = new SimpleModule("Merge duplicated fields in array");
            mergeDuplicatesModule.addDeserializer(JsonNode.class, new MergeDuplicateFieldsJsonNodeDeserializer());
    
            XmlMapper mapper = new XmlMapper();
            mapper.registerModule(mergeDuplicatesModule);
    
            String xml = mapper.writeValueAsString(a);
    
            System.out.println(xml);
            System.out.println(mapper.readTree(xml));
        }
    }
    

    Above code prints:

    String12tomNick
    {"c":"String","as":["1","2","tom","Nick"]}
    

提交回复
热议问题