jackson-dataformat-xml
jackson-dataformat-xml copied to clipboard
XmlMapper does not gather list elements separated with other elements.
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;
}
}
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;
}
}
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);
}