kesem david
kesem david

Reputation: 35

JAXB Wrapped generic object's Setter method NEVER called

I am trying to unmarshall an ArrayList of a generic class called Key.

the Key has setValue() method which recieves a generic parameter.

Key class

@XMLRootElement(name = "Key")
public class Key<T>{

    @XMLElement(name = "Key")
    public setKey(T value){
    this.value = value
    }
}

The specific ArrayList

@XMLElementWrapper(name = "Keys")
@XMLElement(name = "Key")
public setKeys(ArrayList<Key> keys){
this.keys = keys;
}

This part of the XML file

<Keys>
    <Key>2</Key>
</Keys>

Running the code would create the ArrayList and WILL have a single Key object in it. But the Key would be Null.

(Ive tried debugging and could notice that it does not call the setKey() setter of the class)

Anything to do with the fact it's generic? Thanks in advance.

EDIT

In the past day ive debugged this alot, i can say now that the problem is with the fact that after instantiating the ArrayList, while creating each Key per Key Tag in the XML, the unmarshaller uses the Key's empty constructor and just NEVER calls the setter of it, therefore i have an ArrayList containing Keys which their 'value' data member is null.

Can anyone please explain what am I doing wrong? Why does the setter not getting called?

Thank you.

Upvotes: 2

Views: 737

Answers (1)

Pace
Pace

Reputation: 43857

You are probably out of luck. How is the unmarshaller supposed to know that 2 is an integer and not a double or a long or a timestamp or some other class with a custom adapter that can parse 2 into itself.

The annotations you want are basically below (minus the @XmlJavaTypeAdapter which I will explain in a moment) but if you try and run that code without the adapter you will get a NullPointerException because JAXB cannot handle the @XmlValue annotation on an Object (which is how it treats T). The reason JAXB cannot handle it is because it has no way of knowing what the object is.

Now, if you have your own custom rules for determining the type of T (e.g. when coming from XML T is always an Integer or T is an Integer if it doesn't contain a '.' and a Double otherwise) then you can implement your own logic using an adapter which is what I've demonstrated below (I used the second rule).

@XmlRootElement(name="root")
public class SO {

    private List<Key<?>> keys;

    @XmlElementWrapper(name="Keys")
    @XmlElement(name="Key")
    public void setKeys(List<Key<?>> keys) {
        this.keys = keys;
    }

    public List<Key<?>> getKeys() {
        return keys;
    }

    @XmlType
    public static class Key<T> {

        private T val;

        @XmlValue
        @XmlJavaTypeAdapter(ToStringAdapter.class)
        public void setKey(T val) {
            this.val = val;
        }

        public String toString() {
            return "Key(" + val + ")";
        }

    }

    public static class ToStringAdapter extends XmlAdapter<String, Object> {

        @Override
        public Object unmarshal(String v) throws Exception {
            if(v.contains(".")) {
                return Double.parseDouble(v);
            } else {
                return Integer.parseInt(v);
            }
        }

        @Override
        public String marshal(Object v) throws Exception {
            return v.toString(); //Will never be called anyway so you could also throw an exception here
        }

    }

    private static final String XML_INT = "<root><Keys><Key>2</Key></Keys></root>";
    private static final String XML_DOUBLE = "<root><Keys><Key>2.7</Key></Keys></root>";

    public static void main(String [] args) throws Exception {
        JAXBContext jaxbContext = JAXBContext.newInstance(Key.class, SO.class);
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        SO so = (SO) unmarshaller.unmarshal(new StringReader(XML_INT));
        System.out.print(so.keys);
        System.out.println(" " + so.keys.get(0).val.getClass().getSimpleName());
        so = (SO) unmarshaller.unmarshal(new StringReader(XML_DOUBLE));
        System.out.print(so.keys);
        System.out.println(" " + so.keys.get(0).val.getClass().getSimpleName());
    }

}

Upvotes: 1

Related Questions