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

前端 未结 2 957
面向向阳花
面向向阳花 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:03

    Try using below code sample

    This sample xml file

    <?xml version="1.0" encoding="UTF-8"?>
    <UsrAuthentRs>
     <Body>
      <BankUsrInfo>
         <RoleInfo>
            <RoleId>901</RoleId>
         </RoleInfo>
         <RoleInfo>
            <RoleId>902</RoleId>
         </RoleInfo>
         <RoleInfo>
            <RoleId>903</RoleId>
         </RoleInfo>
         <RoleInfo>
            <RoleId>904</RoleId>
         </RoleInfo>
         <RoleInfo>
            <RoleId>905</RoleId>
         </RoleInfo>
         <RoleInfo>
            <RoleId>906</RoleId>
         </RoleInfo>
         <RoleInfo>
            <RoleId>907</RoleId>
         </RoleInfo>
         <RoleInfo>
            <RoleId>908</RoleId>
         </RoleInfo>
         <RoleInfo>
            <RoleId>909</RoleId>
         </RoleInfo>
         <RoleInfo>
            <RoleId>910</RoleId>
         </RoleInfo>
         <RoleInfo>
            <RoleId>911</RoleId>
         </RoleInfo>
      </BankUsrInfo>
      </Body>
    

    package com.inma.itp.queue.models;
    
    import java.util.List;
    
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    
    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
    
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @XmlRootElement(name = "UsrAuthentRs")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class UsrAuthentRs {
    
    
    @XmlElement(name = "Body")
    private Body body = new Body();
    
    @Data
    public static class Body {
        @XmlElement(name = "BankUsrInfo")
        private BankUsrInfo bankUsrInfo = new BankUsrInfo();
    
    
        @Data
        @XmlAccessorType(XmlAccessType.FIELD)
        public static class BankUsrInfo {
    
    
            @XmlElement(name = "RoleInfo")
            @JacksonXmlElementWrapper(useWrapping = false)
            private List<RoleInfo> roles;
    
    
            @Data
            @NoArgsConstructor
            @XmlAccessorType(XmlAccessType.FIELD)
            public static class RoleInfo {
                @XmlElement(name = "RoleId")
                private String roleId;
    
            }
        }
    }
    
    }
    

    This main method which you can convert xml file to Pojo class

    XmlMapper mapper = new XmlMapper();
    UsrAuthentRq deserializedData = xmlMapper.readValue(YouXmlHere, UsrAuthentRq.class);
    
    0 讨论(0)
  • 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<String, List> 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:

    <A><c>String</c><as>1</as><as>2</as><as>tom</as><as>Nick</as></A>
    {"c":"String","as":["1","2","tom","Nick"]}
    
    0 讨论(0)
提交回复
热议问题