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

No String-argument constructor/factory method to deserialize from String value when it is a Integer #254

Closed
mrdgsmith opened this issue Jul 31, 2017 · 5 comments
Milestone

Comments

@mrdgsmith
Copy link

I am trying to deserialise the following xml into the objects:

<?xml version="1.0" encoding="iso-8859-1" ?>
<foo>
    <dean>
        <bar>28</bar>
        <bar>31</bar>
    </dean>
</foo>

My classes are the following

public class Foo {

    private final Dean dean;

    public Foo(@JacksonXmlProperty(localName = "dean") final Dean dean) {
        this.dean = dean;
    }

    public Dean getDean() {
        return dean;
    }
}`
public class Dean {

    @JacksonXmlElementWrapper(useWrapping = false)
    private final List<Bar> bar;

    public Dean(@JacksonXmlProperty(localName = "bar") final List<Bar> bar) {
        this.bar = bar;
    }

    public List<Bar> getBar() {
        return bar;
    }
}
public class Bar {

  @JacksonXmlText
    private Integer value;

    public Bar(@JacksonXmlProperty(localName = " ") Integer value) {
        this.value = value;
    }


    public Integer getValue() {
        return value;
    }
}

My mapper dependencies are the following:

ext {
    jacksonVersion = "2.8.9"
}
compile(group: "com.fasterxml.jackson.dataformat", name: "jackson-dataformat-xml", version: "$jacksonVersion")
compile(group: "com.fasterxml.jackson.datatype", name: "jackson-datatype-jsr310", version: "$jacksonVersion")

Here is the failing test with exception being thrown by the xmlMapper

@Test
    public void shouldParseAndCreateObject() throws Exception {
        final JacksonXmlModule jacksonXmlModule = new JacksonXmlModule();
         XmlMapper xmlMapper = (XmlMapper) new XmlMapper(jacksonXmlModule)
                .disable(FAIL_ON_UNKNOWN_PROPERTIES, FAIL_ON_IGNORED_PROPERTIES);
        Foo foo = xmlMapper.readValue("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>\n" +
                "<foo>\n" +
                "    <dean>\n" +
                "        <bar>28</bar>\n" +
                "        <bar>31</bar>\n" +
                "    </dean>\n" +
                "</foo>", Foo.class);
        assertThat(foo.getDean().getBar().get(0).getValue(), is(28));
    }
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of Bar: no String-argument constructor/factory method to deserialize from String value ('28')
 at [Source: out/test/resources/test.xml; line: 4, column: 16] (through reference chain: service.Foo["dean"]->service.Dean["bar"]->java.util.ArrayList[0])

From reading the exception it looks like the mapper is treating the value 28 as a string rather than a Integer but if i change the Bar class to the following and add an attribute to element bar to the raw xml the same test passes.

public class Bar {

    private String test;

    @JacksonXmlText
    private Integer value;

    public Bar(@JacksonXmlProperty(localName = "test", isAttribute = true) String test, @JacksonXmlProperty(localName = " ") Integer value) {
        this.test = test;
        this.value = value;
    }

    public String getTest() {
        return test;
    }

    public Integer getValue() {
        return value;
    }
<?xml version="1.0" encoding="iso-8859-1" ?>
<foo>
    <dean>
        <bar test="haha1">28</bar>
        <bar test="haha2">31</bar>
    </dean>
</foo>
@Test
    public void shouldParseAndCreateObject() throws Exception {
        final JacksonXmlModule jacksonXmlModule = new JacksonXmlModule();
         XmlMapper xmlMapper = (XmlMapper) new XmlMapper(jacksonXmlModule)
                .disable(FAIL_ON_UNKNOWN_PROPERTIES, FAIL_ON_IGNORED_PROPERTIES);
        Foo foo = xmlMapper.readValue("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>\n" +
                "<foo>\n" +
                "    <dean>\n" +
                "        <bar test=\"haha1\">28</bar>\n" +
                "        <bar test=\"haha2\">31</bar>\n" +
                "    </dean>\n" +
                "</foo>" +
                "</foo>", Foo.class);
        assertThat(foo.getDean().getBar().get(0).getValue(), is(28));
    }

I would say that the Mapper should infer the type from the constructor parameter type and try to instantiate that object with the string value and under the hood do something like Integer.valueOf("28")

@cowtowncoder
Copy link
Member

With quick glance I agree, it seems like your use case should work. Problem probably comes from nesting involved.

One thing on troubleshooting is that it is usually good idea to keep FAIL_ON_UNKNOWN_PROPERTIES enabled: disabling that can hide useful clues regarding things going wrong.

Thank you for reporting this issue. I hope to look into it soon.

@cowtowncoder
Copy link
Member

Ohh. I think I know the problem here: there is no way to link creator (constructor) property and backing value field:

  1. By default, constructor and method parameters do not have names (this is not true for Java 8, with `jackson-module-parameter-names)
  2. Linkage, in absence of such name, has to be via name provided by @JsonProperty or @JacksonXmlProperty
  3. But, alas, here we have value for field and for constructor parameter #0 (actually it gets more complicated than this, because @XmlText has its own separate synthetic name but...)

Couple of ways to make this working are:

  1. Just comment out constructor altogether; auto-generated 0-args constructor will be used for instantiation, and then Jackson uses annotated field for assignment
  2. Leave constructor in but remove @JacksonXmlProperty AND add explicit 0-args constructor (may be protected or private) -- need to add since compiler only adds it if no other ctor exists

In either case, constructor will not be used, and downside I guess is that it is not possible to make field final.

There is still something strange about interaction with @XmlText and constructors, so I'll try to figure out if things could be improved.

@RicardoRB
Copy link

@cowtowncoder I tried with the recommendations what still not working, any other way to make it works?

@cowtowncoder
Copy link
Member

Note to self: problem is the fact that XML has no notion of values other than Strings (and nesting via elements, attributes), so Jackson just sees a String token and does not like that -- while there is coercion from Strings to numbers this only works for simple values but not delegation model.

But it would be reasonable to support such usage for non-typed formats like XML (and CSV as well), if I can figure out a clean way to achieve that.

@cowtowncoder cowtowncoder added 2.12 and removed 2.11 labels May 13, 2020
@cowtowncoder cowtowncoder added this to the 2.12.0 milestone Jun 30, 2020
@cowtowncoder cowtowncoder changed the title no String-argument constructor/factory method to deserialize from String value when it is a Integer No String-argument constructor/factory method to deserialize from String value when it is a Integer Jun 30, 2020
@chenzhendong
Copy link

I still get error on jackson-dataformat-xml-2.12.0-rc1, the xml I want to parse xml like

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