Generic Wrapper
We will create a generic wrapper class with a List property annotated with @XmlAnyElement(lax=true). The type of the object used to populate this list will be based on its root element (see: Using @XmlAnyElement to Build a Generic Message).
package blog.list.wrapper; import java.util.*; import javax.xml.bind.annotation.XmlAnyElement; public class Wrapper<T> { private List<T> items; public Wrapper() { items = new ArrayList<T>(); } public Wrapper(List<T> items) { this.items = items; } @XmlAnyElement(lax=true) public List<T> getItems() { return items; } }
Java Model
Below is the Java model that we'll use for this example. With this approach we need to annotate each class that can appear in the root level collection with @XmlRootElement.
Address
In the Address class we will use the default element names that JAXB implementations derive from classes and properties.
package blog.list.wrapper; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Address { private String street; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } }
Person
Here we will explicitly specify the element names that will be used.
package blog.list.wrapper; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name="PERSON") public class Person { private String name; @XmlElement(name="NAME") public String getName() { return name; } public void setName(String name) { this.name = name; } }
XML
The following XML files will be the input to the demo code. There are a couple of items to note about the XML:
- There are multiple root elements (addresses and PERSONS) that we need to unmarshal to the same class.
- The elements corresponding to items in the collection (address and PERSON) correspond to the @XmlRootElement annotations on Address and Person.
addresses.xml
<?xml version="1.0" encoding="UTF-8"?> <addresses> <address/> <street>1 Some Street</street> </address> <address/> <street>2 Another Road</street> </address> </addresses>
persons.xml
<?xml version="1.0" encoding="UTF-8"?> <PERSONS> <PERSON> <NAME>Jane Doe</NAME> </PERSON> <PERSON> <NAME>John Smith</NAME> </PERSON> </PERSONS>
Demo
The demo code below demonstrates how to use the Wrapper class. Since the root element can be different you will need to specify that you want to unmarshal to the wrapper class. Alternatively you could leverage the @XmlElementDecl annotation to associate multiple root elements with the wrapper class (see: JAXB and Root Elements).
package blog.list.wrapper; import java.util.List; import javax.xml.bind.*; import javax.xml.namespace.QName; import javax.xml.transform.stream.StreamSource; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Wrapper.class, Person.class, Address.class); // Unmarshal Unmarshaller unmarshaller = jc.createUnmarshaller(); List<Address> addresses = unmarshal(unmarshaller, Address.class, "addresses.xml"); List<Person> persons = unmarshal(unmarshaller, Person.class, "persons.xml"); // Marshal Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshal(marshaller, addresses, "addresses"); marshal(marshaller, persons, "PERSONS"); } /** * Unmarshal XML to Wrapper and return List value. */ private static <T> List<T> unmarshal(Unmarshaller unmarshaller, Class<T> clazz, String xmlLocation) throws JAXBException { StreamSource xml = new StreamSource(xmlLocation); Wrapper<T> wrapper = (Wrapper<T>) unmarshaller.unmarshal(xml, Wrapper.class).getValue(); return wrapper.getItems(); } /** * Wrap List in Wrapper, then leverage JAXBElement to supply root element * information. */ private static void marshal(Marshaller marshaller, List<?> list, String name) throws JAXBException { QName qName = new QName(name); Wrapper wrapper = new Wrapper(list); JAXBElement<Wrapper> jaxbElement = new JAXBElement<Wrapper>(qName, Wrapper.class, wrapper); marshaller.marshal(jaxbElement, System.out); } }
Output
Below is the output from running the demo code.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <addresses> <address/> <street>1 Some Street</street> </address> <address/> <street>2 Another Road</street> </address> </addresses> <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <PERSONS> <PERSON> <NAME>Jane Doe</NAME> </PERSON> <PERSON> <NAME>John Smith</NAME> </PERSON> </PERSONS>
Further Reading
If you enjoyed this post then you may also be interested in the following:
Blaise,
ReplyDeleteThis looks interesting - is there any way to signal to the XML Schema generator what the plural name should be? For example if I have a JAX-RS method that returns:
@GET
public Wrapper get() {};
This will fail to generate the matching XML Schema definition in the WADL because Wrapper isn't a @XmlRootElement. Is there any obvious mechanism which would statically allow us to specify the plural name / namespace without access to the Marshall/Unmarshall mechanism.
I guess a set of trivial sub classes would solve this problem; but I was interested to hear if you had another solution that wasn't obvious.
Thanks,
Gerard
Hi Gerard,
DeleteYou could have multiple @XmlElementDecl annotations that correspond to the Wrapper class on an ObjectFactory (a class annotated with @XmlRegistry).
See:
- JAXB and Root Elements
-Blaise
except it doesn't work with enums. so dissappointed with moxy's enum handling. it is just terrible. i switched to moxy for json, now it broke all the classes having list of enums
DeleteCan you enter a bug for the issue you are seeing with enums? Also if you are able to attach a small example that demonstrates the problem that would be a big help:
Delete- https://bugs.eclipse.org/bugs/enter_bug.cgi?product=EclipseLink
Nice job Blaise, I am no expert in XML but was using it to store my test data and your code has saved me numerous wrappers that had no real application use, only testing. Thanks!
DeleteThank you! Exactly what I was looking for!
ReplyDelete