Hi guys,
I'm currently facing an odd behavior with how polymorphic XML objects are
deserialized when using Jackson 2.13.1. The deserialization fails when the
type attribute is not the first attribute in the setting tag. This seems
odd, as Jackson can even properly determine the correct type. My issue with
this case is that my code has to handle XML documents that are generated by
a source I don't have control over and it sadly generates XML documents
where the type attribute is put last. So for example the following XML
works:
<setting type="localizedString" key="testKey">
<value locale="default">testValue</value>
<value locale="de-DE">testValue deutsch</value>
</setting>
and the following XML fails:
<setting key="testKey" type="localizedString">
<value locale="default">testValue</value>
<value locale="de-DE">testValue deutsch</value>
</setting>
with the following error:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected
non-whitespace text ('testValue' in Array context: should not occur (or
should be handled)
at [Source: (StringReader); line: 2, column: 42] (through reference chain:
ch.michael.experimental.JacksonInheritanceOrder$LocalizedSetting["value"])
at
com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:392)
at
com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:351)
at
com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1821)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:315)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:214)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:186)
at
com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer.deserialize(WrapperHandlingDeserializer.java:122)
at
com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:144)
at
com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:110)
at
com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263)
at
com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
at
com.fasterxml.jackson.dataformat.xml.deser.XmlDeserializationContext.readRootValue(XmlDeserializationContext.java:91)
at
com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
at
com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629)
at
com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3597)
at
ch.michael.experimental.JacksonInheritanceOrder.executeTest(JacksonInheritanceOrder.java:143)
at
ch.michael.experimental.JacksonInheritanceOrder.deserializeTrailingType(JacksonInheritanceOrder.java:128)
at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native
Method)
at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at
org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at
org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at
org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at
org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at
org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at
org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at
org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at
org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:93)
at
org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:529)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected
non-whitespace text ('testValue' in Array context: should not occur (or
should be handled)
at [Source: (StringReader); line: 2, column: 42]
at
com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2391)
at
com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser.nextToken(FromXmlParser.java:847)
at
com.fasterxml.jackson.core.util.JsonParserSequence.nextToken(JsonParserSequence.java:151)
at
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:346)
at
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
at
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
at
com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313)
... 43 more
For demo purposes I have create the following little demo test:
import static org.junit.Assert.assertEquals;
import java.util.LinkedList;
import java.util.List;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
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.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
public class JacksonInheritanceOrder {
@JsonInclude(JsonInclude.Include.NON_NULL)
@JacksonXmlRootElement(localName = "setting")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include =
JsonTypeInfo.As.EXISTING_PROPERTY, property = Setting.ATTRIBUTE_TYPE)
@JsonSubTypes({@Type(value = LocalizedSetting.class, name =
LocalizedSetting.TYPE)})
public static interface Setting {
public static final String ATTRIBUTE_TYPE = "type";
@JacksonXmlProperty(localName = "key", isAttribute = true)
public String getKey();
public void setKey(String key);
@JacksonXmlProperty(localName = ATTRIBUTE_TYPE, isAttribute = true)
public String getType();
}
public static class LocalizedSetting implements Setting {
public static final String TYPE = "localizedString";
@JacksonXmlProperty(localName = "key", isAttribute = true)
private String key;
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "value")
private List<LocalizedValue> localizedValues = new LinkedList<>();
@Override
public String getKey() {
return key;
}
@Override
public void setKey(String key) {
this.key = key;
}
public List<LocalizedValue> getLocalizedValues() {
return localizedValues;
}
public void setLocalizedValues(List<LocalizedValue> values) {
this.localizedValues = values;
}
@Override
public String getType() {
return TYPE;
}
}
@JacksonXmlRootElement(localName = "value")
public static class LocalizedValue {
@JacksonXmlProperty(localName = "locale", isAttribute = true)
private String locale;
@JacksonXmlText
private String value;
public String getLocale() {
return locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@BeforeClass
public static void setUpBeforeClass() throws Exception {}
@AfterClass
public static void tearDownAfterClass() throws Exception {}
@Before
public void setUp() throws Exception {}
@After
public void tearDown() throws Exception {}
private static final String XML_TMPL = "<setting%s key=\"testKey\"%s>\n"
+ "<value locale=\"default\">testValue</value>\n"
+ "<value locale=\"de-DE\">testValue deutsch</value>\n"
+ "</setting>";
/**
* Fails with JsonMappingException?!
*
* @throws Exception
*/
@Test
public void deserializeTrailingType() throws Exception {
executeTest(String.format(XML_TMPL, "", " type=\"localizedString\""));
}
/**
* Works as expected.
*
* @throws Exception
*/
@Test
public void deserializeLeadingType() throws Exception {
executeTest(String.format(XML_TMPL, " type=\"localizedString\"", ""));
}
private void executeTest(String xmlInput) throws Exception {
XmlMapper xmlMapper = new XmlMapper();
Setting setting = xmlMapper.readValue(xmlInput, Setting.class);
assertEquals("testKey", setting.getKey());
assertEquals(LocalizedSetting.class, setting.getClass());
List<LocalizedValue> values =
((LocalizedSetting)setting).getLocalizedValues();
assertEquals(2, values.size());
assertEquals("default", values.get(0).getLocale());
assertEquals("testValue", values.get(0).getValue());
assertEquals("de-DE", values.get(1).getLocale());
assertEquals("testValue deutsch", values.get(1).getValue());
}
}
Is this a bug or known issue? Can I work around it?
--
You received this message because you are subscribed to the Google Groups
"jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/jackson-user/381835aa-4730-45df-b6c6-7c06243b3e67n%40googlegroups.com.