Can not deserialize unwrapped list when @JacksonXmlProperty.localName matches @JacksonXmlRootElement.localName
deserialization, jackson, java, serialization
Solution
Works if you write a custom deserializer.
package api.core.jasper;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
public class UnitTest {
@Test
public void test() throws Exception {
final ObjectMapper mapper = new XmlMapper();
final XmlTest before =
new XmlTest("0", Lists.newArrayList(new XmlTest("0.1", null),
new XmlTest("0.2", Lists.newArrayList(new XmlTest("0.2.1", null)))));
System.out.println(before);
final String xml = mapper.writeValueAsString(before);
System.out.println(xml);
final XmlTest after = mapper.readValue(xml, XmlTest.class);
System.out.println(after);
}
@JsonDeserialize(using = XmlTestDeserializer.class)
@JacksonXmlRootElement(localName = "test")
public static class XmlTest {
@JacksonXmlProperty(localName = "id", isAttribute = true)
public String id;
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "test")
public List<XmlTest> children;
public XmlTest(final String id, final List<XmlTest> children) {
this.id = id;
this.children = Optional.fromNullable(children).or(Lists.<XmlTest>newArrayList());
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.add("children", children)
.toString();
}
}
public static class XmlTestDeserializer extends StdDeserializer<XmlTest> {
protected XmlTestDeserializer() {
super(XmlTest.class);
}
@Override
public XmlTest deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException,
JsonProcessingException {
if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
throw new IOException("Invalid token, expected START_OBJECT");
}
String id = null;
final List<XmlTest> children = Lists.newArrayList();
while (jp.nextToken() != JsonToken.END_OBJECT) {
final String key = jp.getCurrentName();
jp.nextToken();
if ("id".equals(key)) {
id = jp.readValueAs(String.class);
} else if ("test".equals(key)) {
final XmlTest child = jp.readValueAs(XmlTest.class);
if (child != null) {
children.add(child);
}
}
}
jp.close();
return new XmlTest(id, children);
}
}
}
Test console output:
XmlTest{id=0, children=[XmlTest{id=0.1, children=[]}, XmlTest{id=0.2, children=[XmlTest{id=0.2.1, children=[]}]}]}
<test id="0"><test id="0.1"/><test id="0.2"><test id="0.2.1"/></test></test>
XmlTest{id=0, children=[XmlTest{id=0.1, children=[]}, XmlTest{id=0.2, children=[XmlTest{id=0.2.1, children=[]}]}]}
Problem
Alright friends... I'm having a hard time wrapping my head around this one. I have an XML document representing a type `XmlTest`. `XmlTest` has a property `children` of type `List<XmlTest>`. `children` is an unwrapped XML collection. The issue is that when I deserialize my XML source as a `XmlTest` instance it fails with: gist ``` java.lang.IllegalStateException: Current state not XML_START_ELEMENT (1) but 6 at com.fasterxml.jackson.dataformat.xml.deser.XmlTokenStream.repeatStartElement(XmlTokenStream.java:228) at com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser.addVirtualWrapping(FromXmlParser.java:280) at com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer._configureParser(WrapperHandlingDeserializer.java:140) at com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer.deserialize(WrapperHandlingDeserializer.java:108) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:230) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:207) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:23) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:464) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:376) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:977) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:276) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121) at com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer.deserialize(WrapperHandlingDeserializer.java:109) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2888) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2034) at a.test.utilities.XmlHelpers.fromXml(XmlHelpers.java:19) at a.UnitTest.test(UnitTest.java:22) ``` Here is the XML document I am using: gist ``` <test id="0"> <test id="0.1"> <test id="0.1.1" /> </test> <test id="0.2" /> <test id="0.3"> <test id="0.3.1" /> </test> </test> ``` Here is the unit test I am using to cause the exception: gist ``` package api.core.jasper; import static org.junit.Assert.fail; import java.util.List; import org.junit.Test; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import com.google.common.collect.Lists; public class UnitTest { @Test public void test() throws Exception { final ObjectMapper mapper = new XmlMapper(); final XmlTest before = new XmlTest("0", Lists.newArrayList(new XmlTest("0.1", null), new XmlTest("0.2", Lists.newArrayList(new XmlTest("0.2.1", null))))); final String xml = mapper.writeValueAsString(before); final XmlTest after = mapper.readValue(xml, XmlTest.class); fail(); } @JacksonXmlRootElement(localName = "test") public static class XmlTest { private final String id; private final List<XmlTest> children; @JsonCreator public XmlTest(@JsonProperty("id") final String id, @JsonProperty("tests") final List<XmlTest> children) { this.id = id; this.children = children; } @JsonProperty("id") @JacksonXmlProperty(localName = "id", isAttribute = true) public String id() { return id; } @JsonProperty("children") @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "test") public List<XmlTest> children() { return children; } } } ``` How can I deserialize this XML document into a `XmlTest` instance? EDIT 1 I have found that changing the name of the root element in the XML document will result in successful deserialization. This is interesting behavior but is not an option for me, I do not control the construction of the document in my application. This will deserialize as expected: gist ``` <root id="0"> <test id="0.1"> <test id="0.1.1" /> </test> <test id="0.2" /> <test id="0.3"> <test id="0.3.1" /> </test> </root> ```