Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XML parser error with nested same element names #294

Closed
softkot opened this issue May 23, 2018 · 15 comments
Closed

XML parser error with nested same element names #294

softkot opened this issue May 23, 2018 · 15 comments
Milestone

Comments

@softkot
Copy link

softkot commented May 23, 2018

Jackson version 2.9.5

I have a simple xml like

<levels>
    <sublevel>
        <id>1</id>
        <sublevel>Name A</sublevel>
    </sublevel>
    <sublevel>
        <id>2</id>
        <sublevel>Name B</sublevel>
    </sublevel>
</levels>

Note that child sublevel has a property sublevel (element with the same name as enclosing tag). That structure has an issue for XmlMapper causing an exception

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
 at [Source: (StringReader); line: 4, column: 9] (through reference chain: RootLevel["sublevel"]->java.util.ArrayList[0]->Sublevel["sublevel"])

XmlMapper configured as

                XmlMapper().apply {
                    registerModule(JaxbAnnotationModule())
                    registerModule(JacksonXmlModule())
                    enable(SerializationFeature.INDENT_OUTPUT)
                    disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                }
                        .setDefaultUseWrapper(false)
                        .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                        .setSerializationInclusion(JsonInclude.Include.NON_EMPTY)

And POJO objects annotated as

    @XmlAccessorType(XmlAccessType.NONE)
    data class RootLevel(
            @field:XmlElement val sublevel: List<Sublevel> = arrayListOf()
    ) : Serializable

    @XmlAccessorType(XmlAccessType.NONE)
    data class Sublevel(
            @field:XmlElement val id: String?=null,
            @field:XmlElement val sublevel: String?=null
    ) : Serializable

@cowtowncoder
Copy link
Member

I'd need to see plain Java version of the definition, otherwise this would be ... Kotlin? ... issue

@softkot
Copy link
Author

softkot commented May 23, 2018

I can try with plain Java later but the same annotated code works just fine with JAXBContext unmarshaller instead of XmlMapper and you can assume i've post a plain java examples :)

@cowtowncoder
Copy link
Member

One thing to note is that Jackson is not a JAXB implementation (nor claims to be one). JAXB annotations are supported as configuration source, so there is sometimes assumption that behavior is (expected to be) identical. This is not to say there is attempt to work differently, just that there are sometimes differences due to bit different goals and history (JAXB being XML-specific framework, Jackson starting with json background).

The biggest commonly encountered difference, which may be relevant here, is that Jackson defaults to "wrapped" Lists, whereas JAXB defaults to "unwrapped".

One thing that has helped me debug issues is to start by serializing content from POJO (POKO?), and having a look if that structure differs from structure you'd expect to read.

@softkot
Copy link
Author

softkot commented May 24, 2018

Here is a sample unit test in pure Java that creates an XML from object and try to parse it back. Finnaly i got the same exception.

INFO: <levels><sublevel><id>1</id><sublevel>Name A</sublevel></sublevel><sublevel><id>2</id><sublevel>Name B</sublevel></sublevel></levels>

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
 at [Source: (byte[])"<levels><sublevel><id>1</id><sublevel>Name A</sublevel></sublevel><sublevel><id>2</id><sublevel>Name B</sublevel></sublevel></levels>"; line: 1, column: 29] (through reference chain: XmlMapperTest$RootLevel["sublevel"]->java.util.ArrayList[0]->XmlMapperTest$Sublevel["sublevel"])

@cowtowncoder
Copy link
Member

@softkot Thank you.

@softkot
Copy link
Author

softkot commented Jun 4, 2018

Any plans to fix it ?

@cowtowncoder
Copy link
Member

(copied here for convenience)

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 org.junit.Test;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
 
public class XmlMapperTest {
    private Logger logger = Logger.getLogger("XmlMapperTest");
 
    @JacksonXmlRootElement(localName = "levels")
    static class RootLevel {
 
        @JacksonXmlElementWrapper(useWrapping = false)
        @JacksonXmlProperty(localName = "sublevel")
        List<Sublevel> sublevels = new ArrayList<>();
    }
 
    @JacksonXmlRootElement(localName = "sublevel")
    static class Sublevel {
 
        @JacksonXmlProperty
        Integer id;
 
        @JacksonXmlProperty
        String sublevel;
    }
 
    Sublevel newSublevel(Integer id, String sublevel) {
        Sublevel res = new Sublevel();
        res.id = id;
        res.sublevel = sublevel;
        return res;
    }
 
    @Test
    public void parserTest() throws IOException {
        RootLevel tree = new RootLevel();
        tree.sublevels.add(newSublevel(1, "Name A"));
        tree.sublevels.add(newSublevel(2, "Name B"));
        XmlMapper mapper = new XmlMapper();
        String xml = mapper.writeValueAsString(tree);
        logger.info(xml);
        RootLevel resTree = mapper.readValue(xml.getBytes(), RootLevel.class);
        logger.info(Integer.toString(resTree.sublevels.size()));
    }
}

@cowtowncoder
Copy link
Member

@softkot I hope to find time to look into this in near future: it is one of top items, but my time is limited on working for Jackson, and there is one set of security vulnerabilities I need to get fixed to get 2.9.6 release.

Thank you for adding all the information here, I hope to get this fixed soon.

@cowtowncoder
Copy link
Member

cowtowncoder commented Jun 5, 2018

Hmmh. Odd. Changing order of properties sublevel and id makes test pass... as does renaming sublevel as anything other than that name. I wonder if it manages to accidentally close the outer scope or something.

@JulienHoueix
Copy link

Is there at least a workaround to avoid this error? For example by using a custom serializer (I tried but it did not work)?

@JulienHoueix
Copy link

The fix I suggest in #301 fixes this problem.

@tenebras
Copy link

Hello,

my problem not exactly the same but may be caused by same reason. I tried to write tests for enums annotated with @JsonCreator and got the same exception: "com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of SimpleEnum out of START_OBJECT token
at [Source: (StringReader); line: 1, column: 1]"

Jackson 2.9.8

Both serialization and deserialization works perfectly for complex objects containing this enums but enums itself fails.

What I found so far:
IntegerDeserializer::_parseInteger (located in NumberDeserializers) doesn't have switch case for START_OBJECT. I suppose it's because JSON {1} is incorrect value. How it works with complex types I don't know, but I'll try to figure out.

Sample code to reproduce:

import static org.junit.Assert.assertEquals;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.IOException;
import java.util.Objects;
import org.junit.Test;

public class EnumTest {

  private final XmlMapper xmlMapper = new XmlMapper();

  // Failing
  @Test
  public void it_should_deserialize_simple() throws IOException {

    final String xmlString = xmlMapper.writeValueAsString(SimpleEnum.FOO);
    System.out.println(xmlString);

    assertEquals(SimpleEnum.FOO, xmlMapper.readValue(xmlString, SimpleEnum.class));
  }

  // Failing
  @Test
  public void it_should_deserialize_complex() throws IOException {

    final String xmlString = xmlMapper.writeValueAsString(ComplexEnum.BAR);
    System.out.println(xmlString);

    assertEquals(ComplexEnum.BAR, xmlMapper.readValue(xmlString, ComplexEnum.class));
  }

  // Works fine
  @Test
  public void it_should_deserialize_object() throws IOException {
    final SomeObject object = new SomeObject();
    final String xmlString = xmlMapper.writeValueAsString(object);
    System.out.println(xmlString);

    assertEquals(object, xmlMapper.readValue(xmlString, SomeObject.class));
  }
}


enum SimpleEnum {
  FOO
}

enum ComplexEnum {
  BAR(18);

  @JsonValue
  private final int value;

  ComplexEnum(int value) {
    this.value = value;
  }

  @JsonCreator
  public static ComplexEnum forValue(int value) {
    if (value == 18) {
      return BAR;
    }

    throw new IllegalArgumentException("Unsupported value");
  }
}

class SomeObject {

  public SimpleEnum simpleEnum = SimpleEnum.FOO;
  public ComplexEnum complexEnum = ComplexEnum.BAR;

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    SomeObject that = (SomeObject) o;
    return simpleEnum == that.simpleEnum && complexEnum == that.complexEnum;
  }

  @Override
  public int hashCode() {
    return Objects.hash(simpleEnum, complexEnum);
  }
}

Output (just in case):

<SomeObject><simpleEnum>FOO</simpleEnum><complexEnum>18</complexEnum></SomeObject>
<SimpleEnum>FOO</SimpleEnum>

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `SimpleEnum` out of START_OBJECT token
 at [Source: (StringReader); line: 1, column: 1]

	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1343)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1139)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1093)
	at com.fasterxml.jackson.databind.deser.std.EnumDeserializer._deserializeOther(EnumDeserializer.java:267)
	at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:206)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
	at EnumTest.it_should_deserialize_simple(EnumTest.java:20)
	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:564)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

<ComplexEnum>18</ComplexEnum>

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `int` out of START_OBJECT token
 at [Source: (StringReader); line: 1, column: 1]

	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1343)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1139)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1093)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$IntegerDeserializer._parseInteger(NumberDeserializers.java:531)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$IntegerDeserializer.deserialize(NumberDeserializers.java:474)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$IntegerDeserializer.deserialize(NumberDeserializers.java:452)
	at com.fasterxml.jackson.databind.deser.std.FactoryBasedEnumDeserializer.deserialize(FactoryBasedEnumDeserializer.java:111)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
	at EnumTest.it_should_deserialize_complex(EnumTest.java:29)
	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:564)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)


Process finished with exit code 255

Best regards

@cowtowncoder cowtowncoder added 2.10 and removed 2.9 labels Oct 3, 2019
@cowtowncoder cowtowncoder added this to the 2.10.0 milestone Oct 3, 2019
@douglasmedia
Copy link

douglasmedia commented Mar 27, 2020

Hi,

Is this issue still present?

I am facing a similar problem with the following xml:

<ValueData>
    <ParameterNumber xsi:type="xsd:int">1234</ParameterNumber>
    <Name>Test</Name>
    <Value xsi:type="xsd:double">0</Value>
</ValueData>
public class ValueData {

    @JacksonXmlProperty(localName = "ParameterNumber")
    private long parameterNumber;

    @JacksonXmlProperty(localName = "Name")
    private String name;

    @JacksonXmlProperty(localName = "Value")
    private double value;
}

I got following error message:

Cannot deserialize instance of `long` out of START_OBJECT token

When adding a custom converter, eg:

    @JacksonXmlProperty(localName = "ParameterNumber")
    @JsonDeserialize(converter = StringToLongConverter.class)
    private long parameterNumber;

everything is fine.

Same issue with double. Any ideas?

I am using jackson-core, jackson-databind and jackson-dataformat-xml in version 2.10.3.

Best regards

@cowtowncoder cowtowncoder added 2.11 and removed 2.10 labels Mar 27, 2020
@cowtowncoder
Copy link
Member

@douglasmedia I think the problem is existence fo both attribute and element for property: I don't think this is quite the same issue. I would suggest filing a separate issue since it might be little bit easier to tackle, given that xsi:type can probably be handled in special way, being part of "well-known" XML Schema attributes. Not saying fix is necessarily trivial or (more importantly) that I have time to work on this in near future, but whenever and whoever does have time could probably model fix similar to how xsi:nil is being (partially) handled.

@cowtowncoder cowtowncoder modified the milestones: 2.10.0, 2.11.1 May 12, 2020
@cowtowncoder
Copy link
Member

Same problem as #393, or at least same fix: will be included in 2.11.1.

@cowtowncoder cowtowncoder changed the title XML parser error XML parser error with nested same element names May 12, 2020
@cowtowncoder cowtowncoder removed the 2.11 label May 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants