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

MismatchedInputException for nested repeating element name in List #393

Closed
kaizen-horse opened this issue Apr 8, 2020 · 13 comments
Closed
Milestone

Comments

@kaizen-horse
Copy link

kaizen-horse commented Apr 8, 2020

I want to deserialize the string contents to Testclass.class on jackson 2.9.10
but I got a Exception: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.lang.String out of START_OBJECT token
at [Source: (StringReader); line: 11, column: 7] (through reference chain: saas.mall.supplier.zowoyoo.TestClass["prices"]->saas.mall.supplier.zowoyoo.TestClass$Prices["price"]->java.util.ArrayList[0]->saas.mall.supplier.zowoyoo.TestClass$Price["price"])

The Class that I want to deserialize contains a list of fields with the same name as the fields of the object in the list。

Here is the test case

    @Test
    public void testXml() throws IOException {
        String content = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "\n" +
                "<result>\n" +
                "  <prices>\n" +
                "    <price>\n" +
                "      <num>100</num>\n" +
                "      <price>7.0</price>\n" +
                "    </price>\n" +
                "  </prices>\n" +
                "</result>";
        com.fasterxml.jackson.dataformat.xml.XmlMapper xmlMapper =
                new com.fasterxml.jackson.dataformat.xml.XmlMapper();
        TestClass testClass = xmlMapper.readValue(content, TestClass.class);
        System.out.println(testClass);
    }

this is the Class I want to deserialize

package test;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
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 java.util.ArrayList;
import java.util.List;


@JsonInclude(JsonInclude.Include.NON_NULL)
public class TestClass {
    /**
     *
     */
    private Prices prices = new Prices();

    public void setPrices(Prices prices) {
        this.prices = prices;
    }

    @JacksonXmlProperty(localName = "prices")
    public Prices getPrices() {
        return this.prices;
    }

    @JacksonXmlRootElement(localName = "price")
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Price {
        /**
         * 0.01
         */
        private String price;

        /**
         * 469
         */
        private String num;

        public void setPrice(String price) {
            this.price = price;
        }

        @JacksonXmlProperty(localName = "price")
        public String getPrice() {
            return this.price;
        }

        public void setNum(String num) {
            this.num = num;
        }

        @JacksonXmlProperty(localName = "num")
        public String getNum() {
            return this.num;
        }
    }

    @JacksonXmlRootElement(localName = "prices")
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Prices {
        /**
         *
         */
        private List<Price> price = new ArrayList<Price>();

        public void setPrice(List<Price> price) {
            this.price = price;
        }

        @JacksonXmlElementWrapper(useWrapping = false)
        @JacksonXmlProperty(localName = "price")
        public List<Price> getPrice() {
            return this.price;
        }
    }
}

PS: if I change the TestClass.Price.price‘s field type from String to Map,it works.like follwing

    @JacksonXmlRootElement(localName = "price")
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Price {
        /**
         * 0.01
         */
        private Map price;


        /**
         * 469
         */
        private String num;


        public void setPrice(Map price) {
            this.price = price;
        }

        @JacksonXmlProperty(localName="price")
        public Map getPrice(){
            return this.price;
        }

        public void setNum(String num) {
            this.num = num;
        }

        @JacksonXmlProperty(localName="num")
        public String getNum (){
            return this.num;
        }
    }

but when I use a string that contains more objects,
I got a exception:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of java.util.LinkedHashMap (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('4.0')
at [Source: (StringReader); line: 11, column: 18] (through reference chain: test.TestClass["prices"]->test.TestClass$Prices["price"]->java.util.ArrayList[1]->test.TestClass$Price["price"])

    @Test
    public void testXml() throws IOException {
        String content = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "\n" +
            "<result>\n" + " <prices>\n" + " <price>\n" + " <num>100</num>\n" +
            " <price>7.0</price>\n" + " </price>\n" + " <price>\n" +
            " <num>100</num>\n" + " <price>4.0</price>\n" + " </price>" +
            " </prices>\n" + "</result>";
        com.fasterxml.jackson.dataformat.xml.XmlMapper xmlMapper = new com.fasterxml.jackson.dataformat.xml.XmlMapper();
        TestClass testClass = xmlMapper.readValue(content, TestClass.class);
        System.out.println(testClass);
    }

Hope to provide a solution,TKS

@lucanus81
Copy link

I have exactly the same issue. If your XML schema allows it, just try to change the name of one of the clashing names. If you cannot, then I think we need a fix.

@kaizen-horse
Copy link
Author

I have exactly the same issue. If your XML schema allows it, just try to change the name of one of the clashing names. If you cannot, then I think we need a fix.

I couldn't find a solution and also my XML schema don't allows change the name, so I switched to DOM4J for deserialization,but It is complicate.A fix is better for me

@lucanus81
Copy link

lucanus81 commented Apr 26, 2020

Hi,
I think I found a solution. I have just discovered that for some weird/unknown reason, that

  • "price" field (String) must be the first field in the "class Price", and even weirder,
  • the xml you're trying to deserialise <price>123</price> must be the first tag.

In that example, I just had to change:

<price><num>123</num><price>123</price></price> to
<price><price>123</price><num>123</num></price>.

Notice that <price> must precede <num>. This must also be true in the class Price as well.

This is the full example:

    @JacksonXmlRootElement(localName = "price")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@Data
	@NoArgsConstructor
	static class Price {
		private String price;
		private String num;
	}

	@JacksonXmlRootElement(localName = "prices")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@NoArgsConstructor
	@Data
	static class Prices {
		@JacksonXmlElementWrapper(useWrapping = false)
		@JacksonXmlProperty(localName = "price")
		private List<Price> price = new ArrayList<Price>();
	}

	@JacksonXmlRootElement(localName = "result")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@Data
	@NoArgsConstructor
	static class Result {
		private Prices prices;
	}

	@Test
	public void testXml() throws IOException {
		String content = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "\n"
				+ "<result>\n" 
				+ "<prices>\n"
				+ " <price>\n" + " <price>7.0</price>\n" + " <num>100</num>\n" + " </price>\n"
                            // As you see, I have changed the order of <price> and <num>:
				//+ " <price>\n" + " <num>100</num>\n" + " <price>7.0</price>\n" + " </price>\n" 
				+ " <price>\n" + " <price>4.0</price>\n" + " <num>100</num>\n" + " </price>" + " "
				+ "</prices>\n" 
				+ "</result>";
		Result testClass = mapper.readValue(content, Result.class);
		System.out.println(testClass);
		
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
		mapper.writeValue(output, testClass);
		System.out.println(output);
	}

Output:

XMLDeserializerTests.Result(prices=XMLDeserializerTests.Prices(price=[XMLDeserializerTests.Price(price=7.0, num=100), XMLDeserializerTests.Price(price=4.0, num=100)]))
<result>
  <prices>
    <price>
      <price>7.0</price>
      <num>100</num>
    </price>
    <price>
      <price>4.0</price>
      <num>100</num>
    </price>
  </prices>
</result>

I really hope this can help you somehow.

@cowtowncoder cowtowncoder changed the title Bug during XML deserialization : "com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.lang.String out of START_OBJECT token MismatchedInputException: Cannot deserialize instance of java.lang.String out of START_OBJECT token Apr 27, 2020
@cowtowncoder
Copy link
Member

I hope to look into this soon, but one think I would suggest first is to use a newer version of Jackson: either 2.10.3 or newly released 2.11.0. Both have a few fixes beyond 2.9.10.

@lucanus81
Copy link

@cowtowncoder: I was using 2.10.2, but can reproduce the same issue on 2.11.0-RC1.

@cowtowncoder
Copy link
Member

cowtowncoder commented Apr 28, 2020

I can reproduce this, including some simplification. I can also see that if I first serialize structure, output matches input you'd expect (i.e. definition seems correct).
Don't yet know underlying reason although suspect it is due to mismatch for unwrapped-list handling, probably related to use of same name (price) in nested scope: that should be perfectly valid use, but perhaps something gets confused internally.

cowtowncoder added a commit that referenced this issue Apr 28, 2020
@kaizen-horse
Copy link
Author

Hi,
I think I found a solution. I have just discovered that for some weird/unknown reason, that

  • "price" field (String) must be the first field in the "class Price", and even weirder,
  • the xml you're trying to deserialise <price>123</price> must be the first tag.

In that example, I just had to change:

  • the xml you're trying to deserialise <price>123</price> must be the first tag.

the produce enviroment can't not change the xml message,so 'the xml you're trying to deserialise 123 must be the first tag.' I can't match this rule.Anyway, thanks a lot

@cowtowncoder
Copy link
Member

@kaizenHorse I agree that this should not be necessary, but I think adding that note may be helpful for some users. But this issue should be resolved and I hope to do that once I have time to figure out exact root cause.

@kaizen-horse
Copy link
Author

@kaizenHorse I agree that this should not be necessary, but I think adding that note may be helpful for some users. But this issue should be resolved and I hope to do that once I have time to figure out exact root cause.
ok,tks,I get the message,how can I add that note in a best way?

@cowtowncoder
Copy link
Member

Hmmmh. Technically, I think this must be due to low-level detection of "wrapper-less" arrays, which creates virtual wrapper for databind to use. And in this case use of price at inner level too gets things confused. Wasn't caught before as names have been distinct.

@cowtowncoder cowtowncoder changed the title MismatchedInputException: Cannot deserialize instance of java.lang.String out of START_OBJECT token MismatchedInputException for nested repeating element name in List May 12, 2020
@cowtowncoder cowtowncoder added this to the 2.11.1 milestone May 12, 2020
@petervandenbroek
Copy link

petervandenbroek commented Nov 12, 2020

This issue still seems to be occur on classes that use Jaxb annotations (generated by XJC).

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Prices", propOrder = {
    "price"
})
@Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v2.3.2")
public class Prices {

    @XmlElement(name = "Price")
    @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v2.3.2")
    protected List<Price> price;

    @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v2.3.2")
    public List<Price> getPrice() {
        if (price == null) {
            price = new ArrayList<Price>();
        }
        return this.price;
    }
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Price", propOrder = {
    "price"
})
@Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v2.3.2")
public class Price {

    @XmlElement(name = "Price")
    @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v2.3.2")
    protected BigDecimal price;

    @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v2.3.2")
    public BigDecimal getPrice() {
        return price;
    }

    @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v2.3.2")
    public void setPrice(BigDecimal value) {
        this.price = value;
    }
}

This leads to the same exception as mentioned in this issue:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.math.BigDecimal out of START_OBJECT token

Is there any way of getting this to work?

@cowtowncoder
Copy link
Member

cowtowncoder commented Nov 12, 2020

@petervandenbroek that is not a complete reproduction of an issue (specifically, input XML is missing) so I can't really say. But couple of notes:

  1. Jackson's default handling of List values differs a bit from JAXB in that wrapping of values is the default -- this can lead to issues
  2. If you can reproduce the problem please open new issue: I don't usually re-open closed issues after fix has been released (leads to difficulties with release notes) in general; and in many cases underlying issue is not the same. You can add a reference from new issue to old one as possibly related
  3. To troubleshoot structural issues it is usually best to create a case where you first serialize value, to see what Jackson expects as compatible value. Jackson does not claim it can read any and all XML structure, but it should be able to "read what it writes".

@petervandenbroek
Copy link

@cowtowncoder, I've created new ticket #433 for this problem, since I am able to reproduce it with a simple test.

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

4 participants