简体   繁体   中英

jackson custom deserializer only getting last value in list xml

I have the following xml

<root>
<date>112004</date>
<entries>
    <entry id = 1>
        <status>Active</status>
        <person>
            <Name>John</Name>
            <Age>22</Age>
        </person>
    </entry>
    <entry id = 2>
        <status>Active</status>
        <person>
            <Name>Doe</Name>
            <Age>32</Age>
        </person>
    </entry>
    <entry id = 3>
        <status>N/A</status>
    </entry>
</entries>

I am using custom jackson deserializer to get the values and pojo looks like

@JacksonXmlRootElement(localName="root", namespace="namespace")

class root { private String date;

@JacksonXmlProperty(localName = "entries", namespace="tns")
private List<Entry> entries;
//getter and setter

}

class Entry {
 private String id;
 private String status;
 private Person person;
 //getter and setter
}

And the deserializer code looks like

public class DeSerializer extends StdDeserializer<root>
{
 protected DeSerializer() {
    super(root.class);
  }
  @Override
public root deserialize(JsonParser p, DeserializationContext ctxt)  throws IOException, JsonProcessingException {
    JsonNode nodes = p.readValueAsTree();
    ObjectMapper mapper = new ObjectMapper();
    List<Entry> entry = mapper.convertValue(nodes.findValues("entry"), new TypeReference<List<Entry>>() {});
}
}

main()
{
XmlMapper x = new XmlMapper();
 final SimpleModule module = new SimpleModule("configModule",   com.fasterxml.jackson.core.Version.unknownVersion());
            module.addDeserializer(root.class, new DeSerializer());
            x.registerModule(module);
            root r = x.readValue(xmlSource, root.class); /*xmlsource is xml as string*/
}

The issue is when I debugged I am always getting the entry last value from xml. So the values of nodes (in deserializer) is {"date":"112004","entries":{"entry":{"id":"3","status":"N/A"}}} and I am not sure why it is not treating as list. I did add annotation of unwrapped = false for List but that did not work out.

It seems readValueAsTree does not support fetching whole collection.

I did some work around without custom DeSerializer which works.

@JacksonXmlRootElement(localName="root")
public class Root {
@JacksonXmlElementWrapper(useWrapping = true)
private List<Entry> entries;

private String date;

public List<Entry> getEntries() {
    return entries;
}

public void setEntries(List<Entry> entries) {
    if (this.entries == null){
        this.entries = new ArrayList<>(entries.size());
    }
    this.entries.addAll(entries);
}

public String getDate() {
    return date;
}

public void setDate(String date) {
    this.date = date;
}
}

class Entry {
private String id;
private String status;
private Person person;
}

class Person {
@JacksonXmlProperty(localName = "Name")
private String name;

@JacksonXmlProperty(localName = "Age")
private String age;
}

Then unit test:

    @Test
    void test_xml_XmlMapper() throws Exception {
    JacksonXmlModule xmlModule = new JacksonXmlModule();
    xmlModule.setDefaultUseWrapper(false);
    ObjectMapper xmlMapper = new XmlMapper(xmlModule);

    String xmlContent = "your xml file here"
    Root bean = xmlMapper.readValue(xmlContent, Root.class);
    assertThat(bean.getEntries().size(), Matchers.equalTo(3));
}

I can see why the answer by @stephen-hazra is downvoted, because he doesn't really answer the question, however: He is right.

I don't know the exact from version, but up until 2.11.x custom deserialization of an Array in an xml was broken. If we take the xml from the question and implemented a Deserializer and used it like this:

@JsonDeserialize(using = MyDeserializer.class)
private List<MyClass> entries = new ArrayList<>();

If the MyDeserializer class extended from StdSerializer, also like in the question, the deserialize method would always only ever get the last child "entry"-element from the children of entries to process.

In later versions this is fixed, you can now implement the deserialize method like follows:

@Override
public List<MyClass> deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
    final List<MyClass> result = new ArrayList<>();
    final ObjectNode node = jsonParser.getCodec().readTree(jsonParser);
    //entry and extendedEntry are different children in the xml that can be parsed as MyClass
    processNode((ContainerNode) node.get("entry"), jsonParser, result);
    processNode((ContainerNode) node.get("extendedEntry"), jsonParser, result);
    return result;
}

private void processNode(ContainerNode cNode, JsonParser jsonParser, List<MyClass> result) throws IOException {
    if (cNode == null) return;
    //cNode can either be an ObjectNode or an ArrayNode based on how many children there can be found in the xml.
    if (cNode.isArray()) { cNode.iterator().forEachRemaining(node -> {
        try {
            result.add(node.traverse(jsonParser.getCodec()).readValueAs(MyClass.class));
            } catch (IOException e) {
              //whatever
            }
        });
    } else {
        result.add(cNode.traverse(jsonParser.getCodec()).readValueAs(MyClass.class));
    }
}
    

I'm writing this example down as answer, because I was basically stuck a whole afternoon, wondering how I could process these other child elements in my xml, because in my case I also had children in the xml with different tag names, but same content type, so parsing just the content with contentUsing was not sufficient, because that way I lost important information based on the original tag name of the child. And in the end it just turned out that the jackson version 2.11 I was using, was just broken in that regard. So thanks to Stephen for pointing me into that direction.

If someone is still facing this issue, please upgrade to the latest jackson 2.12.3 version. I was facing this issue in 2.11.x version.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM