javajacksonjackson-dataformat-xml

Jackson XML deserialization skips field when using multiple useWrapping = false


I am trying to deserialize the following XML:

<root>
    <foo name="AAA" />
    <bar name="BBB" />
    <foo name="CCC" />
</root>

My Jackson classes are:

@Data
public class Foo {
    @JacksonXmlProperty(isAttribute = true)
    private String name;
}

Bar is identical, just a different class name. (In the real code they are different, this is just an example).

And the root class is

@Data
public class Root {
    @JacksonXmlProperty(localName = "foo")
    @JacksonXmlElementWrapper(useWrapping = false)
    private List<Foo> foos;
    @JacksonXmlProperty(localName = "bar")
    @JacksonXmlElementWrapper(useWrapping = false)
    private List<Bar> bars;
}

When I try to deserialize the XML, using this code

System.out.println(new XmlMapper().readValue(theXml, Root.class));

The result is this (note the lack of "AAA"):

Root(foos=[Foo(name=CCC)], bars=[Bar(name=BBB)])

However, if I move the fields in the XML so that both foo tags are next to each other, it prints

Root(foos=[Foo(name=AAA), Foo(name=CCC)], bars=[Bar(name=BBB)])

I'm using jackson-dataformat-xml 2.11.1 which is the latest.

What is going on here, and how can I fix it?


Solution

  • For any property, you can specify a method to use as the setter or getter using Jackson annotations (JsonSetter and JsonGetter). When you just a need a little modification to what Jackson is doing, then this seems easier that writing a custom deserializer / serializer for the whole class. Jackson also has a JsonAnySetter annotation that is a fallback to use for something not specified in your class (I've found that to be handy sometimes; I've used that to put all XML attributes of a type of element into a single Map rather than having to have properties for every possible attribute).

    You could add custom XML deserialization methods to your Root class. Something like this:

    @JsonSetter(value =  "foo")
    public void setFooFromXml(Foo foo) {
        if (this.foos == null) {
            this.foos = new ArrayList<Foo>();
        } 
        this.foos.add(foo);
    }
    
    @JsonSetter(value =  "bar")
    public void setBarFromXml(Bar bar) {
        if (this.bars == null) {
            this.bars = new ArrayList<Bar>();
        } 
        this.bars.add(bar);
    }
    

    Using Jackson to deserialize the XML like this:

    try {
        String input = "<root><foo name=\"AAA\" /><bar name=\"BBB\" /><foo name=\"CCC\" /></root>";
        XmlMapper mapper = new XmlMapper();
        Root root = mapper.readValue(input, Root.class);
        System.out.println(root.getBars());
        System.out.println(root.getFoos());
        
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    Gives an output of this (after adding some simple toString() and getter methods):

    [Bar [name=BBB]]
    [Foo [name=AAA], Foo [name=CCC]]