Friday, September 2, 2011

JAX-RS: Jersey and JSON single element arrays

Last week I have been struggling with a small issue while developing a service using Jersey. The goal of this service is to provide JSON object to my Web application, so called directly from the browser. This service returns in a JSON array a list of Employees, something like:

{"employee":[
 {"email":"jdoe@example.com","firstName":"John","lastName":"Doe"},
 {"email":"mmajor@example.com","firstName":"Mary","lastName":"Major"}
]}
So an "employee" array, this is perfect and expected, but when my service returns a single element the returned object looks like:
{"employee":{"email":"jdoe@example.com","firstName":"John","lastName":"Doe"}}
As you can see brackets [...] are missing around the employee item. This is an issue since your client code is expecting an array.

A solution...

My application is using Jersey, the JAX-RS Reference Implementation, and JAXB for the serialization of Java Objects to JSON, as I have explained in a previous blog post. I found a solution to this by creating a new JAXB Context Resolver. In this resolver I can control how the JSON object should be generated, here is my implementation :
import com.grallandco.employee.service.converter.EmployeeConverter;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;

import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;

@Provider
public class JAXBContextResolver implements ContextResolver < JAXBContext > {

    private JAXBContext context;
    private Class[] types = {EmployeeConverter.class};

    public JAXBContextResolver() throws Exception {
        this.context = new JSONJAXBContext(JSONConfiguration.mapped().arrays("employee").build(),
                types);

    }

    public JAXBContext getContext(Class objectType) {
        for (Class type : types) {
            if (type == objectType) {
                return context;
            }
        }
        return null;
    }
}

First of all I declare this new class as a @Provider to say that it this class is of interest to the JAX-RS runtime. I put in the types array the list of the Java classes that are concerned by the serialization (line#13). Then I create the ContextResolved with the different options that fulfill my requirements. You can take a look to the JAXBContextResolver Javadoc to see all the possible options available. With this class, the service now returned the following JSON String:
{"employee":[{"email":"jdoe@example.com","firstName":"John","lastName":"Doe"}]}
You can find a complete example (NetBeans project) here.

5 comments:

Dom Derrien said...

IMHO, it's more "JSON' to publish the array without the encapsulating object.

context = new JSONJAXBContext(JSONConfiguration.natural().build(), supportedTypes);

With supportedTypes being a collection of Classes to handle:

static protected Class[] supportedTypes = {
Employee.class,
// etc.
};


Additional bonus: you don't have singular label for a collection that should be introduced by a plural term ;)

A+, Dom

Blaise Doughan said...

Hello,

The issue you are seeing is due to how the default JSON binding is being implemented. The JAXB impl is reporting StAX events to Jettison which is producing JSON. When an element name is reported once it assumes that it is not a JSON array:
- JAXB and JSON via Jettison

You may be interested in the JSON binding being added to EclipseLink JAXB (MOXy):
- JSON Binding with EclipseLink MOXy - Twitter Example
- Binding to JSON & XML - Geocode Example

MOXy integrates very easily with the JAX-RS implementation (Jersey) in GlassFish:
- Creating a RESTful Web Service - Part 3
- MOXy's XML Metadata in a JAX-RS Service

-Blaise

Tug said...

Dom

I do use this syntax to in some of my code.

Blaise,

Thanks for your links


Tug

Anonymous said...

Another less intrusive solution is to change the JSON implementation to Jackson:

http://stackoverflow.com/questions/5641430/jaxb-single-element-in-array

jersey bola said...

Thank you :)