FasterXML / jackson-dataformat-xml

Extension for Jackson JSON processor that adds support for serializing POJOs as XML (and deserializing from XML) as an alternative to JSON
Apache License 2.0
573 stars 222 forks source link

Attribute / element name collision cannot be configured during deserialization #65

Open nezda opened 11 years ago

nezda commented 11 years ago

If an element has an attribute and sub-element with the same name, the attribute seems to be ignored and the element value used, despite collision resolving configuration with @JacksonXmlProperty(localName="state", isAttribute=true) (for colliding name state). Here is concrete example

static class TwoStates {
  @JacksonXmlProperty(localName="state", isAttribute=true)
  public String stateAttr = "NOT SET";
  public String state = "NOT SET";
}

The following input should deserialize the appropriate distinct values into the appropriate distinct fields: <Wrapper state='stateAttr'><state>stateElement</state></Wrapper>

Here is a failing test snippet for com.fasterxml.jackson.dataformat.xml.failing.TestDeserialization

        private static class TwoStates
    {
        @JacksonXmlProperty(localName="state", isAttribute=true)
        public String stateAttr = "NOT SET";
        public String state = "NOT SET";

        @Override public String toString() { return "TwoStates: stateAttr: " + stateAttr + " state: " + state; }
    }

    public void testAttrElementConflict() throws Exception
    {
        TwoStates ob = MAPPER.readValue("<Wrapper state='stateAttr'><state>stateElement</state></Wrapper>",
                TwoStates.class);
        assertNotNull(ob);
                System.err.println("ob: " + ob);
        assertEquals("stateElement", ob.state);
        assertEquals("stateAttr", ob.stateAttr); // attribute stateAttr is ignored so this fails
    }
cowtowncoder commented 11 years ago

Correct -- currently there is no way to handle synonyms (either between attribute or element; or between properties with different namespace). This is unfortunate, but will also be difficult to correct.

arielvalentin commented 9 years ago

I have a similar issue where the source XML has the same attribute name for different namespaces, e.g.

xml <x:someNode xmlns:x="http://x.com/xsd" xmlns:y="http://y.com/xsd" x:version="1" y:version="9" />



As far as I can tell it seems that there both the BeanDeserializer and FromXmlParser have to change to take into account the namespaces. Is that correct?
cowtowncoder commented 9 years ago

Yes; while namespace information is exposed by underlying parser, and although property name can pass information, the way deserializer/property lookup works is to only use local name. And since only XML has additional concept, making things work together is difficult.

arielvalentin commented 9 years ago

@cowtowncoder The other cases I can think of Smile, JSON, and YAML don't support a concept like XML namespaces. It sounds to me like this use case isn't really something Jackson should support and that one should use more specialized XML tools. What is your opinion about that? Do you want to see Jackson change to support namespaces?

cowtowncoder commented 9 years ago

@arielvalentin I am bit torn here, having written a few XML libraries (Woodstox, Aalto). Ideally, Jackson would be able to support exact matching of XML namespaces.

At the same time, due to practical limitations, at this point if use of namespaces is integral (and not just casual, where there are no collisions), it is reasonable to use JAXB for data-binding.

So: if feasible, I would really like to see Jackson handle namespace binding, resolution properly. It just has to be done in a way that works well without requiring other backends to support concept they have no use for. In theory Avro backend also has namespaces, for example; in practice, their use is quite limited as names are never included in encoded data. They are only used at schema level.

kamransaleem commented 3 years ago

I'm stuck on this issue too, unfortunately I cannot change the source XML that i'm deserializing as it's not mine. Any workarounds?

sunoce commented 3 years ago

@kamransaleem I found this thread to be a valid workaround. The answer in this threat uses JsonIgnore and JsonAnySetter :

Before:

public static class CustomResult {

    public CustomResult() {}

   @XmlElement(name = "service")
   private String service;

   @XmlElement(name = "date")
   private String date;

   @XmlElement(name = "status")
   private Integer status;

   @XmlElement(name = "service")
   private Service statusObj;

}

Workaround:

@XmlRootElement(name = "result")
public static class CustomResult {

   public CustomResult() {}

   @JsonIgnore
   private List<Object> service = new ArrayList<>();

   @XmlElement(name = "date")
   private String date;

   @XmlElement(name = "status")
   private Integer status;

   @JsonAnySetter
   public void setServices(String name, Object value) {
       if (value instanceof String) {
           service.add(value);
       }
       if (value instanceof Map) {
           // TODO create new Service object from map entries.
       }
       // error?
   }
}

see here

sunoce commented 3 years ago

Additional hint - if the value is a map, I use another object mapper to convert it to the respective object. If you need more code examples feel free to ask.

mahendranv commented 1 year ago

@sunoce Thanks for the sample it works great!