简体   繁体   中英

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.

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>

For further readers.

I just found an interesting blog entry talking about automatic pluralization of JAX-RS. https://blogs.oracle.com/PavelBucek/entry/returning_xml_representation_of_list

I'm not sure whether this feature is implementation-specific or not.

Everybody must try what and how platforms serve.

@GET
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public List<Item> read() {

    final List<Item> items = ... // is the variable name relevant 

    return items;
}

This could be an another proper way I think.

@GET
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response readItems() {

    final List<Item> items = ...;

    return Response.ok(new GenericEntity<List<String>>(list) {}).build();
}

I'm sorry I found another possible way which I think the better.

@GET
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public List<Item> readItems() {

    final List<Item> items = itemBean.list(...);

    return items;
}

Whit this case, any application server could generate in different ways.

With GlassFish

<items> <!-- smart plural names -->
  <item xmlns="http://www.example.com">
    ...
  </item>
  <item xmlns="http://www.example.com">
    ...
  </item>
<items>

With JBoss

<collection>
  <item xmlns="http://www.example.com">
    ...
  </item>
  <item xmlns="http://www.example.com">
    ...
  </item>
<collection>

For clients, this problem is not an impact. A general XPath expression could be used like this.

"//item/name/first"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM