Team lead for the TopLink/EclipseLink JAXB & SDO implementations, and the Oracle representative on those specifications. Blaise is a DZone MVB and is not an employee of DZone and has posted 44 posts at DZone. You can read more from them at their website. View Full User Profile

MOXy is the New Default JSON-Binding Provider in GlassFish 4

06.14.2013
| 8212 views |
  • submit to reddit

GlassFish 4 is now available offering the complete Java EE 7 (JSR-342) platform. EclipseLink made some major contributions to this release.  The first is providing the JPA 2.1 (JSR-338) implementation.  The second which I'll cover in this post is EclipseLink MOXy is now the default JSON-binding provider for JAX-RS applications.

RESTful Service 

CustomerService

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 CustomerService {
 
    @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.setName("Jane Doe");
         
        PhoneNumber pn = new PhoneNumber();
        pn.setType("work");
        pn.setValue("5551111");
        customer.getPhoneNumbers().add(pn);
         
        return customer;
     }
 
}
CustomerApplication I have used an Application class to specify the path for the service.
package org.example.service;
 
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
  
@ApplicationPath("rest/*")
public class CustomerApplication  extends Application {
 
}
Java Model
Below is the Java model we will use for this example.  The goal is to produce good XML and JSON representations using a single set of metadata.CustomerThere are a couple of interesting things to note about the Customer class.
  1. The id property is an int.  Since JSON has a different representation for numbers and text we will look at how this value gets represented in the JSON output.
  2. The phoneNumbers property is of type List<PhoneNumber>.  It has been annotated with @XmlElementWrapperto produce good XML output.  We will examine the impact of doing this on the JSON output.
package org.example.model;
 
import java.util.*;
import javax.xml.bind.annotation.*;
 
@XmlRootElement
public class Customer {
 
    private int id;
    private String name;
    private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    @XmlElementWrapper
    @XmlElement(name="phoneNumber")
    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }
 
}
PhoneNumber 

In XML the PhoneNumber class maps to a complex type with simple content (see: JAXB and Complex Types with Simple Content).  This means it maps to an XML element with attributes and text.  These XML concepts do not correspond directly to JSON concepts so we will examine the JSON representation.

package org.example.model;
 
import javax.xml.bind.annotation.*;
 
public class PhoneNumber {
 
    private String type;
    private String value;
 
    @XmlAttribute
    public String getType() {
        return type;
    }
 
    public void setType(String type) {
        this.type = type;
    }
 
    @XmlValue
    public String getValue() {
        return value;
    }
 
    public void setValue(String value) {
        this.value = value;
    }
 
}
Using the Service

Request - GET 

Below is the URL we will use to access our service:

http://localhost:8080/CustomerResource/rest/customers/1

Response (application/xml)

Since we mapped our domain model with JAXB (JSR-222) metadata the XML representation below isn't much of a surprise.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer id="1">
    <name>Jane Doe</name>
    <phoneNumbers>
        <phoneNumber type="work">5551111</phoneNumber>
    </phoneNumbers>
</customer>

Response (application/json) - GlassFish 3.1.2

Lets take a look at what the JSON representation looked like back in GlassFish 3.1.2 before MOXy was the default JSON-binding provider.  There are several things to take note of:

  1. The int value for the id property is represented as JSON text.
  2. The phoneNumbers property of type List<PhoneNumber> since it had a single item was represented as a JSON object instead of a JSON array.  Also even when this key contains a collection it is still called phoneNumber.
  3. The JSON key corresponding to the type property since it was annotated with @XmlAttribute is prefixed with @.
  4. The JSON key corresponding to the value property since it was annotated with @XmlValue is called $.
{
    "id": "1",
    "name": "Jane Doe",
    "phoneNumbers": {
        "phoneNumber": {
            "@type": "work",
            "$": "5551111"
        }
    }
}
Response (application/json) - GlassFish 4


Using the default configuration you will see that upgrading to GlassFish 4 eliminates the two biggest problems: 

  1. Numeric Values - Now the id property is correctly marshalled to JSON as a numeric value.  This isn't a trick, MOXy bases the JSON representation on the Java type of the property.  The value property of type String on the instance of PhoneNumber contained only digits and it is correctly marshalled to JSON as text.
  2. Collections of Size 1 - The phoneNumber key is now a JSON array, unfortunately it is still called phoneNumber(I'm demonstrate how to fix this next).
{
    "id": 1,
    "name": "Jane Doe",
    "phoneNumbers": {
        "phoneNumber": [
            "@type": "work",
            "$": "5551111"
        ]
    }
}
Customizing the JSON-Binding


MOXy can now be configured by leveraging the JAX-RS ContextResolver mechanism.  You simply need to return an instance of MOXyJsonConfig.  We will use it to do the following:

  1. Specify that we don't want to prefix the JSON keys that correspond to properties mapped with @XmlAttribute.
  2. Use the JSON key value instead of $ for properties mapped with @XmlValue.
  3. MOXyJsonConfig can also be used to pass properties down to the Marshaller/Unmarshaller.  We will do this to clean up the JSON key for collection properties (see: Binding to JSON & XML - Handling Collections).
package org.example.service;
 
import javax.ws.rs.ext.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.glassfish.jersey.moxy.json.MoxyJsonConfig;
 
@Provider
public class MOXyJsonContextResolver implements ContextResolver<MoxyJsonConfig> {
 
    private final MoxyJsonConfig config;
 
    public MOXyJsonContextResolver() {
        config = new MoxyJsonConfig()
            .setAttributePrefix("")
            .setValueWrapper("value")
            .property(JAXBContextProperties.JSON_WRAPPER_AS_ARRAY_NAME, true);
    }
 
    @Override
    public MoxyJsonConfig getContext(Class<?> objectType) {
        return config;
    }
 
}
New Response


Finally we have a JSON representation that we can be happy with.  It does not contain any XML related artifacts, even though it was produced with the same metadata as the XML representation (which hasn't changed).  All it took was a little MOXy.

{
    "id": 1,
    "name": "Jane Doe",
    "phoneNumbers": [
        {
            "type": "work",
            "value": "5551111"
        }
    ]
}


Further Reading


If you enjoyed this post then you may also be interested in:


Published at DZone with permission of Blaise Doughan, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)