Search code examples
javajsonspringjaxbjaxb2

Please advise the best pattern for serialising JAXB Lists


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.


Solution

  • 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>