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

Jackson fails to instantiate class when only text node is present #608

Open
codemonstur opened this issue Sep 11, 2023 · 12 comments
Open

Jackson fails to instantiate class when only text node is present #608

codemonstur opened this issue Sep 11, 2023 · 12 comments
Labels
2.16 For issues planned for 2.16 has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue

Comments

@codemonstur
Copy link

codemonstur commented Sep 11, 2023

The following code generates an exception:

public class JacksonBug {

    public static final String XML_WORKS_1 = """
            <root>
                <nested>
                    <other>text</other>
                    The text node.
                </nested>
            </root>
            """;
    public static final String XML_WORKS_2 = """
            <root>
                <plain>The text node.</plain>
            </root>
            """;
    public static final String XML_FAILS = """
            <root>
                <nested>The text node.</nested>
            </root>
            """;

    private static class Root {
        public Nested nested;
        public Plain plain;
    }
    private static class Nested {
        @JacksonXmlProperty(isAttribute = false)
        public String other;
        @JacksonXmlProperty(isAttribute = false)
        public String reallyNotHere;
        @JacksonXmlText
        public String text;
    }
    private static class Plain {
        @JacksonXmlText
        public String text;
    }

    public static void main(final String... args) throws JsonProcessingException {
        final var xmlMapper = new XmlMapper();
        xmlMapper.readValue(XML_WORKS_1, Root.class);
        xmlMapper.readValue(XML_WORKS_2, Root.class);
        xmlMapper.readValue(XML_FAILS, Root.class);
    }

}

Setting fields to private or public has no effect. If I add the tag the parsing works fine. If I remove the other and reallyNotHere fields the parsing works fine. But when the child tags are NOT there and the field IS there I get this Exception:

Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `tools.JacksonBug$Nested` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('The text node.')
 at [Source: (StringReader); line: 2, column: 27] (through reference chain: tools.JacksonBug$Root["nested"])
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1739)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1364)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromString(StdDeserializer.java:311)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1504)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:197)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187)
	at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:314)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
	at com.fasterxml.jackson.dataformat.xml.deser.XmlDeserializationContext.readRootValue(XmlDeserializationContext.java:91)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4825)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3772)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3740)
	at tools.JacksonBug.main(JacksonBug.java:50)

Process finished with exit code 1

Jackson version: 2.15.2

@pjfanning
Copy link
Member

Have a look at these search results - https://github.com/search?q=repo%3AFasterXML%2Fjackson-dataformat-xml%20JsonIgnore&type=code

The JsonIgnore annotation and variants of it are what are used in Jackson to ignore fields.

@codemonstur
Copy link
Author

Have a look at these search results - https://github.com/search?q=repo%3AFasterXML%2Fjackson-dataformat-xml%20JsonIgnore&type=code

The JsonIgnore annotation and variants of it are what are used in Jackson to ignore fields.

I don't want to ignore the field. This is a very real field that is sometimes there and sometimes not there. And I need access to the value inside it if it is there.

@pjfanning
Copy link
Member

This looks like the correct behaviour to me. You have a mechanism to get this to work (the XmlElement annotation).
We can't just go and change Jackson behaviour - other users depend on it working the way that it does already. Even if we decide to change it to suit you then you will need to wait for a release.

In the end of the day, you have to accept that libs work a certain way. Jackson is very pluggable - write your own custom deserializer if you don't like the built-in behaviour. Or there are dozens of alternative libs for deserializing XML.

@codemonstur
Copy link
Author

codemonstur commented Sep 11, 2023

No this is definitely not correct behavior. And as far as I can tell there is no way to configure the class to get the data to parse. Just to be clearer on what can happen here, these are both valid XML snippets:

<root>
  <nested>
       <subtag>somedata</subtag>
        A text node
   </nested>
</root>

<root>
   <nested>A text node</nested>
</root>

Both snippets should be parsable using two classes where nested contains one @XmlElement annotated field and one @XmlValue. The second snippet should simply call the default contructor and leave the field to null. Dieing with an error on this input is very much not correct. Fields are allowed to be null, that is normal behavior everywhere else, it is just here where Jackson fails.

@pjfanning
Copy link
Member

Can you find the docs that describe that the code is supposed to work the way you want? If not, then this is not a bug - it is a change request.

@codemonstur
Copy link
Author

I'm not interested in a debate over semantics or what rules have been defined somewhere. If you wish to call it a change request instead of a bug thats fine by me. At the moment I see no way to write a set of classes that can be used to parse the above two snippets. And both snippets are valid XML and structurally similar. I'm fine with an alternative configuration using whatever settings in Jackson, field naming, classes, or annotations may exists. I just want access to my data.

@pjfanning
Copy link
Member

Write a custom deserializer if you can't get it to work with the default jackson-dataformat-xml code. There are examples online of how to write Jackson custom deserializers. Or try another XML lib.

Jackson is a JSON lib that has some support for other formats. There is no great community around jackson-dataformat-xml. It is what it is. Even if someone feels the need to do something on this issue, it could be weeks, months or years until that happens.

@cowtowncoder cowtowncoder added the 2.16 For issues planned for 2.16 label Sep 12, 2023
@cowtowncoder
Copy link
Member

cowtowncoder commented Sep 12, 2023

Quick note on POJO: you are declaring private (and, final...) fields besides no-args constructor -- how would values be expected to be passed to Nested class?

But other than that, handling Mixed Content (both XML Text and XML Element(s) at same level) is very difficult for data-binding. It is possible some cases may not be supportable.

Still, what would help here would be fail reproduction using only Jackson annotations (use of JAXB annotations for actual use is fine, but for unit tests we ideally need Jackson equivalents). This would eliminate possibility of issue being due to JAXB annotation support (and if so, moving issue there) and avoid problem with cross module dependencies (XML module does not depend on JAXB annotation support).

@codemonstur
Copy link
Author

Quick note on POJO: you are declaring private (and, final...) fields besides no-args constructor -- how would values be expected to be passed to Nested class?

The classes are marked with the @XmlAccessorType(FIELD) annotation which instructs the serializer to set the fields directly instead of going through a constructor or setter. I prefer this way since there is less code and it makes it clear to the reader that these classes are immutable DTOs and only set through serialization.

But other than that, handling Mixed Content (both XML Text and XML Element(s) at same level) is very difficult for data-binding. It is possible some cases may not be supportable.

The configuration is unambiguous and works well in all other circumstances. If the child tag exists Jackson can parse the XML just fine. The @XmlValue annotation is being used the way it should and Jackson clearly supports it. And if the child tag isn't there the field could be set to null, the same way a field is normally set to null when a tag is absent.

Still, what would help here would be fail reproduction using only Jackson annotations (use of JAXB annotations for actual use is fine, but for unit tests we ideally need Jackson equivalents). This would eliminate possibility of issue being due to JAXB annotation support (and if so, moving issue there) and avoid problem with cross module dependencies (XML module does not depend on JAXB annotation support).

I want it to work with JAXB annotations (for reasons..). That said, I automatically assumed it would be the parser itself where the problem lies instead of the annotation project. Should I create another bug report at com.fasterxml.jackson.module:jackson-module-jaxb-annotations?

@cowtowncoder
Copy link
Member

No need to create/move issue, but the test case really needs to use Jackson annotations. This should be fine as there are equivalents.

But on fields: Jackson does not recognize or use @XmlAccessorType annotation. Use of @XmlElement etc, however, does make Jackson discover fields -- but they should not be final as final fields should not be settable outside of constructors. So all that needs to change for test is to keep them non-final.

I don't think either of these things is the root cause of the issue.

@codemonstur
Copy link
Author

@cowtowncoder You are right JAXB was an unnecessary complication. The same problem exists when using just jackson annotations and the standard XmlMapper. To make you happy I also removed the finals :). I edited the original question at the top with the new code.

@cowtowncoder cowtowncoder added the has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue label Sep 15, 2023
@cowtowncoder
Copy link
Member

Thank you @codemonstur

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.16 For issues planned for 2.16 has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue
Projects
None yet
Development

No branches or pull requests

3 participants