jackson-dataformat-xml icon indicating copy to clipboard operation
jackson-dataformat-xml copied to clipboard

Attribute / element name collision cannot be configured during deserialization

Open nezda opened this issue 11 years ago • 9 comments

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
    }

nezda avatar May 13 '13 23:05 nezda

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.

cowtowncoder avatar May 14 '13 01:05 cowtowncoder

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?

arielvalentin avatar Mar 03 '15 00:03 arielvalentin

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.

cowtowncoder avatar Mar 03 '15 04:03 cowtowncoder

@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?

arielvalentin avatar Mar 03 '15 04:03 arielvalentin

@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.

cowtowncoder avatar Mar 03 '15 05:03 cowtowncoder

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?

kamransaleem avatar Jul 14 '21 12:07 kamransaleem

@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 avatar Jul 16 '21 13:07 sunoce

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.

sunoce avatar Jul 16 '21 14:07 sunoce