There have been many questions as to how why list types are not serialising, however I'm questioning what is a good practice to serve a list type of beans in a simple way.
So far I have been creating inner classes to support the wrapper, though I don't like the plumbing as it were as I then need to do it for every pojo.
A customer class may look as follows:
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
private int id;
private String name;
// field accessors
@XmlRootElement(name = "customers")
@XmlAccessorType(XmlAccessType.FIELD)
public static final class CustomerList {
private List<Customer> customer;
public CustomerList() {
this.customer = new ArrayList<>();
}
public DataList(List<Customer> list) {
this.customer = list;
}
// customer accessors.
}
}
I tried making a generic class like XmlList<T>
and create new instances on return but JAXB appears to not like this.
I'm using this in a Spring/MVC RESTful application where I need to support both JSON and XML. My JSON should be represented as an array, which allows this method to easily facilitate both by placing the implementation inside the JSON call and then wrapping with the XML call.
Here comes how I do this.
@XmlRootElement // or @XmlTransient if you want to
public class Plural<S> {
public static <P extends Plural<S>, S> P newInstance(
final Class<P> pluralType, final Collection<S> elms) {
P lt = (P) pluralType.newInstance();
lt.singulars = new ArrayList<>(elms);
return lt;
}
protected Collection<S> getSingulars() {
if (singulars == null) {
singulars = new ArrayList<S>();
}
return singulars;
}
private Collection<S> singulars;
}
Then you can make any required plural types of any singular types. Maybe you don't like that you should make all those plural classes for all singular types but it could be really helpful especially when you want to be look more nice to those client developers.
@XmlRootElement
public class Customers extends Plural<Customer> {
@XmlElement(name = "customer")
public Collection<Customer> getCustomers() {
return getSingulars();
}
}
@XmlRootElement
public class Items extends Plural<Item> {
@XmlElement(name = "item")
public Collection<Item> getItems() {
return getSingulars();
}
}
@XmlRootElement
public class Invoices extends Plural<Invoice> {
@XmlElement(name = "invoice")
public Collection<Invoice> getInvoices() {
return getSingulars();
}
}
@XmlRootElement
public class BrettRyans extends Plural<BrettRyan> {
@XmlElement(name = "brettRyan")
public Collection<BrettRyan> getBrettRyans() {
return getSingulars();
}
}
UPDATE per Brett Ryan's comment
Here comes fully featured source codes.
You can see full mavenized project at http://code.google.com/p/jinahya/source/browse/trunk/com.googlecode.jinahya/stackoverflow/
JAXB doesn't need the setter if you control to use fields not properties.
@XmlTransient
public class Plural<S> {
public static <P extends Plural<S>, S> P newInstance(
final Class<P> pluralType) {
return newInstance(pluralType, Collections.<S>emptyList());
}
public static <P extends Plural<S>, S> P newInstance(
final Class<P> pluralType, final Collection<? extends S> singulars) {
try {
final P plural = pluralType.newInstance();
plural.getSingulars().addAll(singulars);
return plural;
} catch (InstantiationException ie) {
throw new RuntimeException(ie);
} catch (IllegalAccessException iae) {
throw new RuntimeException(iae);
}
}
protected Collection<S> getSingulars() {
if (singulars == null) {
singulars = new ArrayList<S>();
}
return singulars;
}
private Collection<S> singulars;
}
@XmlAccessorType(XmlAccessType.NONE)
public class Item {
public static Item newInstance(final long id, final String name) {
final Item instance = new Item();
instance.id = id;
instance.name = name;
return instance;
}
@Override
public String toString() {
return id + "/" + name;
}
@XmlAttribute
private long id;
@XmlValue
private String name;
}
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement
public class Items extends Plural<Item> {
@XmlElement(name = "item")
public Collection<Item> getItems() {
return getSingulars();
}
}
testing...
public class ItemsTest {
@Test
public void testXml() throws JAXBException, IOException {
final Items marshallable = Plural.newInstance(Items.class);
for (int i = 0; i < 5; i++) {
marshallable.getItems().add(Item.newInstance(i, "name" + i));
}
for (Item item : marshallable.getItems()) {
System.out.println("marshallable.item: " + item);
}
final JAXBContext context = JAXBContext.newInstance(Items.class);
final Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
marshaller.marshal(marshallable, baos);
baos.flush();
final Unmarshaller unmarshaller = context.createUnmarshaller();
final Items unmarshalled = (Items) unmarshaller.unmarshal(
new ByteArrayInputStream(baos.toByteArray()));
for (Item item : unmarshalled.getItems()) {
System.out.println("unmarshalled.item: " + item);
}
}
}
prints
marshallable.item: 1/name1
marshallable.item: 2/name2
marshallable.item: 3/name3
marshallable.item: 4/name4
unmarshalled.item: 0/name0
unmarshalled.item: 1/name1
unmarshalled.item: 2/name2
unmarshalled.item: 3/name3
unmarshalled.item: 4/name4
UPDATE per Brett Ryan's 2nd comment
@Test
public void testXsd() throws JAXBException, IOException {
final JAXBContext context = JAXBContext.newInstance(Items.class);
context.generateSchema(new SchemaOutputResolver() {
@Override
public Result createOutput(final String namespaceUri,
final String suggestedFileName)
throws IOException {
return new StreamResult(System.out) {
@Override
public String getSystemId() {
return "noid";
}
};
}
});
}
When Plural
annotated with @XmlRootElement
.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="items" type="items"/>
<xs:element name="plural" type="plural"/>
<xs:complexType name="items">
<xs:complexContent>
<xs:extension base="plural">
<xs:sequence>
<xs:element name="item" type="item" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="plural">
<xs:sequence/>
</xs:complexType>
<xs:complexType name="item">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="id" type="xs:long" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>
When Plural
annotated with @XmlTransient
.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="items" type="items"/>
<xs:complexType name="items">
<xs:sequence>
<xs:element name="item" type="item" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="item">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="id" type="xs:long" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>