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.

Reply via email to