Java Model
Below is the Java model we will use for this post. The same metadata will be used to customize the XML and JSON produced by our JAX-RS service. The get/set methods have been omitted to save space.
Customer
Below is a simple representation of a customer. I have annotated a couple of the fields with MOXy's @XmlPath extension (see: XPath Based Mapping) to demonstrate where MOXy is really being used.
package org.example.model; import java.util.*; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlAttribute(name="id") private int identifier; @XmlPath("personalInfo/firstName/text()") private String firstName; @XmlPath("personalInfo/lastName/text()") @XmlElement(nillable=true) private String lastName; @XmlElementWrapper @XmlElement(name="phoneNumber") private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); }
PhoneNumber
We will map the PhoneNumber class to a complex type with simple content (see: JAXB and Complex Types with Simple Content) to see the impact on JSON-binding.
package org.example.model; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber { @XmlAttribute private String type; @XmlValue private String value; }
package-info
We will leverage the package level @XmlSchema annotation to namespace qualify the resulting XML (see: JAXB & Namespaces) so that we can see the impact on the JSON representation.
@XmlSchema( namespace="http://www.example.org/model", elementFormDefault=XmlNsForm.QUALIFIED) package org.example.model; import javax.xml.bind.annotation.*;
RESTful Service
CustomerResource
Normally a real service will be backed by JPA to do persistence operations (see: Creating a RESTful Web Service - Part 4/5). But for this post I will use a "Hello World" style service that returns a Customer based on an ID as XML and JSON to illustrate some points about binding.
package org.example.service; import javax.ejb.*; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import org.example.model.*; @Stateless @LocalBean @Path("/customers") public class CustomerResource { @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("{id}") public Customer read(@PathParam("id") int id) { Customer customer = new Customer(); customer.setId(id); customer.setFirstName("Jane"); customer.setLastName(null); PhoneNumber pn = new PhoneNumber(); pn.setType("work"); pn.setValue("5551111"); customer.getPhoneNumbers().add(pn); return customer; } }
CustomerApplication
MOXy is configured as the JSON-binding provider using the MOXyJsonProvider class via a JAX-RS Application class (see: MOXy as your JAX-RS JSON Provider - MOXyJsonProvider). MOXyJsonProvider offers different settings you can use to customize the JSON representation. In this example we will leverage the wrapperAsArrayName property to clean up the representation of collections (see: Binding to JSON & XML - Handling Collections).
package org.example.service; import java.util.*; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider; @ApplicationPath("rest/*") public class CustomerApplication extends Application { @Override public Set<Class<?>> getClasses() { HashSet<Class<?>> set = new HashSet<Class<?>>(1); set.add(CustomerResource.class); return set; } @Override public Set<Object> getSingletons() { MOXyJsonProvider moxyJsonProvider = new MOXyJsonProvider(); moxyJsonProvider.setWrapperAsArrayName(true); HashSet<Object> set = new HashSet<Object>(1); set.add(moxyJsonProvider); return set; } }
Output
We will examine the output we get from calling our service with the following URL using the application/xml and application/json media types.
http://localhost:7001/CustomerResource/rest/customers/1
XML
Below is a sample of the XML output. It isn't a surprise as it exactly matches the JAXB and MOXy metadata that we applied to our model.
<?xml version="1.0" encoding="UTF-8"?> <customer xmlns="http://www.example.org/model" id="1"> <personalInfo> <firstName>Jane</firstName> <lastName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/> </personalInfo> <phoneNumbers> <phoneNumber type="work">5551111</phoneNumber> </phoneNumbers> </customer>
Note the following XML specific things about this output.
- There is a root element (line 2).
- The XML is namespace qualified. (line 3).
- There are XML attributes (lines 3 & 10).
- The id attribute contains a int value (line 3).
- The xsi:nil attribute is used to indicate that the lastName element contains a null value. (lines 6 & 7).
- There are a collection of phoneNumber elements with a grouping element called phoneNumbers (lines 9-11).
JSON
Below is the JSON response when MOXy's JSON-binding is used by leveraging MOXyJsonProvider. It was produced using the exact same metadata as the XML representation, but all the XML specific items are gone and JSON specific items are used instead.
{ "id": 1, "personalInfo": { "firstName": "Jane", "lastName": null }, "phoneNumbers": [ { "type": "work", "value": "5551111" } ] }
Now lets compare the JSON output with what WebLogic would have produced by default. By default WebLogic uses its JAXB implementation with an intermediate library to convert XML events to/from JSON. Since MOXy is the default JAXB implementation in WebLogic the @XmlPath annotation is applied, but we don't get any of the other benefits from MOXy's JSON-binding.
{ "@id": "1", "personalInfo": { "firstName": "Jane", "lastName": { "@nil": "true" } }, "phoneNumbers": { "phoneNumber": { "@type": "work", "$": "5551111" } } }
Below are some of the problems that we see with the default representation that weren't present when we used MOXy's JSON-binding. They are all due to the XML representation leaking into the JSON representation.
- Keys that correspond to properties mapped with @XmlAttribute are prefixed with @ (lines 2 & 11).
- The int value for the id property is incorrectly written as a JSON string (line 2).
- JSON representation of null is not used for the lastName key (lines 5-7).
- Our List of PhoneNumber objects were not marshalled correctly as a JSON array of size 1 (lines 10-13).
- Our phoneNumbers property will map to a JSON key of phoneNumber instead of phoneNumbers (line 10).
Further Reading
If you enjoyed this post then you may also be interested in:
- MOXy is the New Default JSON-Binding Provider in GlassFish 4
- EclipseLink 2.4 Release Available for Download
- EclipseLink MOXy is the JAXB Provider in WebLogic Server 12c
- MOXy as your JAX-RS JSON Provider - MOXyJsonProvider
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.