Question

I have a list setter on a class that's annotated with both @XmlElementWrapper(name = "foos") and @XmlElement(name = "foo").

When I unmarshall XML that has no <foos></foos> or <foo/> elements, the setter is called and passed an empty list. Is there a way to get the following?:

  • When there is no <foos/>, do not call the setter. Or if the setter must be called, pass null.
  • When <foos/> is present but empty, pass an empty list to the setter.
  • When <foos> has one or more child <foo/> elements, pass a populated list.
Was it helpful?

Solution

You could use an XmlAdapter for this use case:

input1.xml

When there is no , do not call the setter. Or if the setter must be called, pass null.

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <child/>
</root>

input2.xml

When is present but empty, pass an empty list to the setter.

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <child>
        <foos/>
    </child>
</root>

input3.xml

When has one or more child elements, pass a populated list.

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

Root

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Root {

    private Child child;

    @XmlJavaTypeAdapter(ChildAdapter.class)
    public Child getChild() {
        return child;
    }

    public void setChild(Child child) {
        this.child = child;
    }

}

Child

import java.util.List;

public class Child {

    private List<String> strings;

    public List<String> getStrings() {
        return strings;
    }

    public void setStrings(List<String> strings) {
        System.out.println("setStrings");
        this.strings = strings;
    }

}

ChildAdapter

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class ChildAdapter extends XmlAdapter<ChildAdapter.AdaptedChild, Child> {

    public static class AdaptedChild {
        public Foos foos;
    }

    public static class Foos {
        public List<String> foo;
    }

    @Override
    public Child unmarshal(AdaptedChild adaptedChild) throws Exception {
        Child child = new Child();
        Foos foos = adaptedChild.foos;
        if(null != foos) {
            List<String> foo = foos.foo;
            if(null == foo) {
                child.setStrings(new ArrayList<String>());
            } else {
                child.setStrings(foos.foo);
            }
        }
        return child;
    }

    @Override
    public AdaptedChild marshal(Child child) throws Exception {
        AdaptedChild adaptedChild = new AdaptedChild();
        List<String> strings = child.getStrings();
        if(null != strings) {
            Foos foos = new Foos();
            foos.foo = strings;
            adaptedChild.foos = foos;
        }
        return adaptedChild;
    }

}

Demo

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        Object o;

        o = unmarshaller.unmarshal(new File("input1.xml"));
        marshaller.marshal(o, System.out);

        o = unmarshaller.unmarshal(new File("input2.xml"));
        marshaller.marshal(o, System.out);

        o = unmarshaller.unmarshal(new File("input3.xml"));
        marshaller.marshal(o, System.out);
    }

}

Output

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <child/>
</root>
setStrings
<?xml version="1.0" encoding="UTF-8"?>
<root>
   <child>
      <foos/>
   </child>
</root>
setStrings
<?xml version="1.0" encoding="UTF-8"?>
<root>
   <child>
      <foos>
         <foo>Hello World</foo>
      </foos>
   </child>
</root>

OTHER TIPS

This is the adapter that ended up working for the complications mentioned in the comments of Blaise Doughan's answer:

public class ListOfFooAdapter extends XmlAdapter<ListOfFooAdapter.Adapted, List<Foo>> {
    @XmlRootElement(name = "foos")
    public static class Adapted {
        public List<Foo> foo;
    }

    @Override
    public List<Foo> unmarshal(Adapted adapted) throws Exception {
        return adapted.foo;
    }

    @Override
    public Adapted marshal(List<Foo> foo) throws Exception {
    if (null == foo) {
            return null;
        } else {
            Adapted adapted = new Adapted();
            adapted.foo = foo;
            return adapted;
        }
    }
}

...the unmarshall method doesn't get called unless the element is present in the XML.

I annotated my list property like so:

@XmlJavaTypeAdapter(ListOfFooAdapter.class)
public List<Foo> getFoos() {
    ...
}

public void setFoos(List<Foo> l) {
    ...
}

I was trying to solve an identical issue and played with the samples here for quite a while. A big thanks for them, quite educational.

However, it didn't seem right that something as mature as the JAXB implementation in JDK 6 would understand the elements in the XML and callously hand me code an empty list.

It turns out JAXB was populating the list after calling my setter with a reference to a still empty list, so that when my setter code looked like this, it missed the subsequent updates to the list during the rest of the unmarshalling phase:

@XmlElementWrapper(name = "foos")
@XmlElement(name = "foo")
public void setFoos(List<Foo> newFoos) {
    this.foos.clear();
    this.foos.addAll(newFoos);
}

When I modified my setter to

@XmlElementWrapper(name = "foos")
@XmlElement(name = "foo")
public void setFoos(List<Foo> newFoos) {
    this.foos = newFoos;
}

it worked just fine. It is always a jittery experience to hang on to someone else's pointer to a list because the list owner can modify it after the fact, but in this case it was that change that fixed the problem.

I confirmed my assumption by iterating through the list passed by JAXB and sure enough it complained about ConcurrentModificationException, evidence that it was indeed working on the list after handing it to my code.

You can place all your logic to the beforeUnmarshall, afterUnmarshall methods or Unmarshaller listener, and it will be executed when unmarshalling is completed.

See https://stackoverflow.com/a/4378648/751200 for more information.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top