JAXB/MOXy: Do not call XmlElementWrapper setter when element missing?
I have a list setter on a class that's annotated with both @XmlElementWrapper(name = "foos") and @XmlElement(name = "foo").
When I unmarshall XML开发者_如何学C 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.
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>
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.
精彩评论