jackson-dataformat-xml icon indicating copy to clipboard operation
jackson-dataformat-xml copied to clipboard

XmlMapper does not gather list elements separated with other elements.

Open Fuud opened this issue 3 years ago • 9 comments

jackson-dataformat-xml 2.13.1

I encountered a problem trying parse Tomcat server.xml using XmlMapper. The problem: server.xml can have multiple Connector declarations. These declaration are children of Service tag but can be separated by other entities. Once them will be separated only the last one will be injected.

I created simple test to reproduce issue (second assertion will fail):


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import org.junit.Test;

import java.util.List;

import static org.junit.Assert.assertEquals;

public class XmlMapperTest {

    @Test
    public void test() throws JsonProcessingException {
        XmlMapper xmlMapper = new XmlMapper();
        xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        String goodStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>   \n" +
                "            <A>                                       \n" +
                "                <B value=\"1\"/>                      \n" +
                "                <B value=\"2\"/>                      \n" +
                "            </A>";
        A good = xmlMapper.readValue(goodStr, A.class);
        assertEquals(2, good.b.size());

        String badStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>   \n" +
                "            <A>                                       \n" +
                "                <B value=\"1\"/>                      \n" +
                "                <C/>                                  \n" +
                "                <B value=\"2\"/>                      \n" +
                "            </A>";
        A bad = xmlMapper.readValue(badStr, A.class);
        assertEquals(2, bad.b.size()); // <------------------------ here will be failure
    }

    static class A {
        @JacksonXmlElementWrapper(useWrapping = false)
        @JacksonXmlProperty(localName = "B")
        List<B> b;
    }

    static class B {
        @JacksonXmlProperty(isAttribute = true)
        int value;
    }
}

Fuud avatar Jun 28 '22 11:06 Fuud

I found a workaround: readToTree and convert to target object:


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import org.junit.Test;

import java.util.List;

import static org.junit.Assert.assertEquals;

public class XmlMapperTest {

    @Test
    public void test_workaround() throws JsonProcessingException {
        XmlMapper xmlMapper = new XmlMapper();
        xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        String goodStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>   \n" +
                "            <A>                                       \n" +
                "                <B value=\"1\"/>                      \n" +
                "                <B value=\"2\"/>                      \n" +
                "            </A>";
        A good = xmlMapper.convertValue(xmlMapper.readTree(goodStr), A.class);
        assertEquals(2, good.b.size());

        String badStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>   \n" +
                "            <A>                                       \n" +
                "                <B value=\"1\"/>                      \n" +
                "                <C/>                                  \n" +
                "                <B value=\"2\"/>                      \n" +
                "            </A>";
        A bad = xmlMapper.convertValue(xmlMapper.readTree(badStr), A.class);
        assertEquals(2, bad.b.size()); // <------------------------ here will be ok
    }

    static class A {
        @JacksonXmlElementWrapper(useWrapping = false)
        @JacksonXmlProperty(localName = "B")
        List<B> b;
    }

    static class B {
        @JacksonXmlProperty(isAttribute = true)
        int value;
    }
}

Fuud avatar Jun 28 '22 11:06 Fuud

Yes, this is an existing limitation.

The issue can also be worked around by having a setter method that appends entries instead of replacing. So something like

List<B> b = new ArrayList<>();

public void setB(List<B> values) {
   this.b.addAll(values);
}

cowtowncoder avatar Jun 28 '22 22:06 cowtowncoder