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

Strange behavior when using XML attributes #206

Closed
yatsykve opened this issue Aug 5, 2016 · 6 comments
Closed

Strange behavior when using XML attributes #206

yatsykve opened this issue Aug 5, 2016 · 6 comments

Comments

@yatsykve
Copy link

yatsykve commented Aug 5, 2016

My jackson version 2.8.1:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.8.1</version>
</dependency>

I want to parse XML like this:

<out>
    <in>
        <first>fff</first>
        <second>sss</second>
    </in>
    <in>
        <first>fff2</first>
        <second>sss2</second>
    </in>
</out>

So my Out class:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
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 lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.List;

@Getter
@Setter
@ToString
@JacksonXmlRootElement(localName = "out")
@JsonIgnoreProperties(ignoreUnknown = true)
public class Out {

    @JacksonXmlProperty(localName = "in")
    @JacksonXmlElementWrapper(useWrapping = false)
    private List<In> ins;

}

In class:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@JsonIgnoreProperties(ignoreUnknown = true)
public class In {

    @JacksonXmlProperty(localName = "first")
    private String first;
    @JacksonXmlProperty(localName = "second")
    private String second;

}

And App class:

import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.IOException;

public class App {

    public static void main(String[] args) throws IOException {
        XmlMapper xmlMapper = new XmlMapper();
        Out out = xmlMapper.readValue(App.class.getResource("/test.xml"), Out.class);
        System.out.println(out);
    }

}

For XML above everything is good, I get Out(ins=[In(first=fff, second=sss), In(first=fff2, second=sss2)]).
But I found few issues:

  1. If I change Out element name to something else everything is working, is it OK?

    <bla-bla>
        <in>
            <first>fff</first>
            <second>sss</second>
        </in>
        <in>
            <first>fff2</first>
            <second>sss2</second>
        </in>
    </bla-bla>

    I get Out(ins=[In(first=fff, second=sss), In(first=fff2, second=sss2)])

  2. Lets add some attribute to first element:

    <bla-bla>
        <in>
            <first lang="en">fff</first>
            <second>sss</second>
        </in>
    </bla-bla>

    And this fail with Can not construct instance of xmlparsing.In: no String-argument constructor/factory method to deserialize from String value ('sss').
    More stranger that if we swap elements then everything will be good:

    <bla-bla>
        <in>
            <second>sss</second>
            <first lang="en">fff</first>
        </in>
    </bla-bla>

    I get Out(ins=[In(first=fff, second=sss)])

  3. But this swap can't help me for a long, because:

    <bla-bla>
        <in>
            <second>sss</second>
            <first lang="en">fff</first>
        </in>
        <in>
            <first>fff2</first>
            <second>sss2</second>
        </in>
    </bla-bla>

    I get only second one : Out(ins=[In(first=fff2, second=sss2)])

  4. One more strange thing:

    <bla-bla>
        <in>
            <first lang="en">fff</first>
            <second></second>
        </in>
    </bla-bla>

    I get two elements: Out(ins=[In(first=fff, second=null), In(first=null, second=null)]). Without lang="en" everything is good.

@cowtowncoder
Copy link
Member

Ok, first things first: not checking of the outermost element is an implementation quirk; element name is not verified. This could be considered a flaw, or a convenience feature; if it was verified, there would be need to figure out expect name from class.

As to attributes: the problem is that addition of an attribute will essentially force handling of the XML element to expect a POJO, not scalar value. Or put another way, scalar values may only be mapped from simple XML elements. I don't remember if there are attempts to make this particular case work, however (I recall something along those lines), but fundamentally this is problematic usage: where would value of lang property go here?

I think there are flaws in handling, so that cases (3) and (4) should either fail to indicate mismatch (if attribute and value case can not be supported), or work. So there are things to investigate. Above just tries to explain what is happening, and not to claim it's the way it should work.

@yatsykve
Copy link
Author

yatsykve commented Aug 9, 2016

OK
changing fields into In.class to some XmlNode like:

@Getter
@Setter
@ToString
@JsonIgnoreProperties(ignoreUnknown = true)
public class In {

    @JacksonXmlProperty(localName = "first")
    private XmlNode first;
    @JacksonXmlProperty(localName = "second")
    private XmlNode second;

}

And XmlNode.class :

@Getter
@Setter
@ToString
@JsonIgnoreProperties(ignoreUnknown = true)
public class XmlNode {

    @JacksonXmlText
    private String value;
    //still ignoring any other attributes

}

solving my problems. I'm not trying it now, because previously I have very similar issue with parsing XML (#191). And that time XmlNode wasn't helpful. I think it will be something similar.
But in any case thank you for answer I think we can close this issue.

@yatsykve yatsykve closed this as completed Aug 9, 2016
@yatsykve
Copy link
Author

yatsykve commented Aug 9, 2016

Or maybe it's better to throw some exception in case of such situation? because now if we add attribute to some field we get weird behavior.

@cowtowncoder
Copy link
Member

@latsyk there should indeed be an exception to indicate actual problem, and not just quietly swallow or fail. I'll reopen this issue to see if there's a way to better catch the problem.

@cowtowncoder
Copy link
Member

I am actually not quite sure what to do here. Trying output with attributes, POJOs, suggests that code will figure it out (by not trying to write things as attributes when it can not).
So I think I'll need more specific problem to work on.

@AnshikaKoul
Copy link

AnshikaKoul commented Jan 24, 2018

Hi @cowtowncoder ,

My jackson version 2.9.3:

<jackson.version>2.9.3</jackson.version>
<dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
</dependency>
<dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>${jackson.version}</version>
</dependency>
<dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jsr310</artifactId>
      <version>${jackson.version}</version>
</dependency>
<dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jdk8</artifactId>
      <version>${jackson.version}</version>
</dependency>
<dependency>
      <groupId>com.fasterxml.jackson.module</groupId>
      <artifactId>jackson-module-kotlin</artifactId>
      <version>${jackson.version}</version>
</dependency>
<dependency>
      <groupId>com.fasterxml.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-xml</artifactId>
      <version>${jackson.version}</version>
</dependency>

Kotlin version : <kotlin.version>1.2.10</kotlin.version>
Ratpack version : <ratpack.kotlin.version>1.1.2</ratpack.kotlin.version>

I too am facing an issue while deserialising an element with an attribute.

Example XML that needs to be parsed :

<Something>
    <first cn="abc">someValue</first>
    <second>3050</second>
</Something>          

classes that I am using :

data class Something(
   @JacksonXmlProperty(localName = "first", namespace = somenamespace)  val first: String
)
class ResponseXmlParser {
 private val xmlMapper = xmlObjectMapper()

  private val log = LoggerFactory.getLogger(javaClass)
  fun parseResponseXml(responseBodyStream: InputStream): Response {
    val jsonNode = xmlMapper.readValue(responseBodyStream, Envelope::class.java)
    log.info("RESPONSE: " + jsonNode.body.response)
    return jsonNode.body.response
  }

  private fun xmlObjectMapper(): XmlMapper {
    val jacksonXmlModule = JacksonXmlModule()
    jacksonXmlModule.setDefaultUseWrapper(false)

    return XmlMapper(jacksonXmlModule)
        .registerModule(KotlinModule())
        .registerModule(JavaTimeModule())
        .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false)
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) as XmlMapper
  }
}

Now in the first element that has both attribute value and textual content. whilst parsing I get this exception:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.order.soap.Something (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('3050')

which is really weird. Coz I have other elements with just attribute value and all of them work just fine. Only this element which has both attribute value and textual content doesn't work.

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

3 participants