Search code examples
javajaxbeclipselinkmoxy

EclipseLink MOXy stores instances of DOM Element for fields of type Object annotated with @XmlElement


We are using MOXy JAXB in our project.

Model class:

@XmlRootElement(name = "field")
@XmlType(propOrder = {"id","value"})
public class FieldData{
    @XmlAttribute
    private String id;
    @XmlAttribute
    private Object value;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }

    public Object getValue() {
        return value;
    }
    public void setValue(Object value) {
        this.value = value;
    }
}

For my use case, I want value to be of type Object itself as I may get any primitive data type value here. I will take them as strings initially. Once I get the object, I do the type conversion and save it into the same field. The above use case is working fine. But when I change @XmlAttribute to @XmlElement it is not working. I see that the value is unmarshalled as an instance of ElementNSImpl. Is there any work around for this?


Solution

  • Here is a brain dump on what you are seeing:


    Demo Code

    I will use the same demo code with the different mappings described below:

    import java.io.File;
    import javax.xml.bind.*;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
            JAXBContext jc = JAXBContext.newInstance(Foo.class);
    
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            File xml = new File("input.xml");
            Foo foo = (Foo) unmarshaller.unmarshal(xml);
    
            System.out.println(foo.getBar().getClass());
        }
    
    }
    

    Use Case #1 - Object Property Mapped With @XmlAttribute

    Java Model

    Foo

    We use the @XmlAttribute annotation to map a property to an @XmlAttribute. Note: This isn't a valid configuration when using the JAXB reference implementation.

    import javax.xml.bind.annotation.*;
    
    @XmlRootElement
    public class Foo {
    
        private Object bar;
    
        @XmlAttribute
        public Object getBar() {
            return bar;
        }
    
        public void setBar(Object bar) {
            this.bar = bar;
        }
    
    }
    

    XML #1

    input.xml

    In the XML document below the bar attribute contains numeric digits.

    <?xml version="1.0" encoding="UTF-8"?>
    <foo bar="123"/>
    

    output

    As there is no typing information (in the XML or in the Java class), MOXy brings the value in as a String. A String is the most concrete type that can represent all possible values on an XML attribute.

    class java.lang.String
    

    XML #2

    input.xml

    In the XML document below the bar attribute contains alphabet characters.

    <?xml version="1.0" encoding="UTF-8"?>
    <foo bar="Hello World"/>
    

    output

    A String is the most concrete type that can represent all possible values on an XML attribute.

    class java.lang.String
    

    Use Case #2 Object Property Mapped With @XmlElement

    Java Model

    Foo

    In this version of the Foo class we will not annotate the bar property, this is the same as annotating it with @XmlElement.

    import javax.xml.bind.annotation.*;
    
    @XmlRootElement
    public class Foo {
    
        private Object bar;
    
        public Object getBar() {
            return bar;
        }
    
        public void setBar(Object bar) {
            this.bar = bar;
        }
    
    }
    

    XML #1 (Simple Element)

    input.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <foo>
        <bar>Hello World</bar>
    </foo>
    

    Output

    class com.sun.org.apache.xerces.internal.dom.ElementNSImpl
    

    XML #2 (Complex Element)

    input.xml

    Instead of containing just test, now the bar element contains XML attributes and child elements.

    <?xml version="1.0" encoding="UTF-8"?>
    <foo>
        <bar a="1">
           <b>2</b>
           <c>3</c>
        </bar>
    </foo>
    

    Output

    Now we start to see why JAXB treats the value as a DOM, the element could be arbitrarily complex so a DOM element becomes a structure that can hold any possible value.

    class com.sun.org.apache.xerces.internal.dom.ElementNSImpl
    

    XML #3 (Typed Element)

    input.xml

    In the XML document below

    <?xml version="1.0" encoding="UTF-8"?>
    <foo xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       <bar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">Hello World</bar>
    </foo>
    

    Output

    class java.lang.String
    

    Use Case #3 Object Property Mapped With @XmlElement(type=String)

    Java Model

    Foo

    In this version of the Foo class we will annotate the bar property with @XmlElement(type=String.class). As far as Java is concerned the property is still of typeObject, but JAXB will treat the property as if it's typeString`.

    import javax.xml.bind.annotation.*;
    
    @XmlRootElement
    public class Foo {
    
        private Object bar;
    
        @XmlElement(type=String)
        public Object getBar() {
            return bar;
        }
    
        public void setBar(Object bar) {
            this.bar = bar;
        }
    
    }
    

    XML #1 (Simple Element)

    Now we see that the value of the bar element is treated as a String.

    input.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <foo>
        <bar>Hello World</bar>
    </foo>
    

    Output

    class java.lang.String