JSON Support

JSON Support Photo
Author: Tatyana Milkina

Jersey JSON support comes as a set of JAX-RS MessageBodyReader and MessageBodyWriter providers distributed with the jersey-JSON module. These providers enable using three basic approaches when working with JSON format: POJO support, JAXB support, and low-level support.

We will overview all approaches to using JSON format in the article. All examples will need a dependency to the jersey-JSON module in pom.xml:

<dependency>
     <groupId>com.sun.jersey</groupId>
     <artifactId>jersey-json</artifactId>
     <version>1.17</version>
</dependency>

1. POJO Support

POJO support represents the most simple way to convert POJO to JSON and back.

Example 1.1. POJO

The Exam is a class of objects, which will be converted to JSON:

public class Exam {
    public int id;
    public String name;

    public Exam() {}
    public Exam(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Example 1.2. RESTful Web Services Jersey JSON example

ExamService's method getExam() is signed as produced MediaType.APPLICATION_JSON:

@Path("exam")
public class ExamService {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Exam getExam() {
        return new Exam(3, "OCEJWSD 6");
    }
}

Example 1.3. Setting JSONConfiguration .FEATURE_POJO_MAPPING feature in web.xml

To use this approach, it is required to turn the JSONConfiguration .FEATURE_POJO_MAPPING feature on. This can be done in web.xml setting the feature in servlet init parameter:

<servlet>
    <servlet-name>Example Component</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>com.examclouds</param-value>
    </init-param>
    <init-param>
        <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>Example Component</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

The ExamService resource can be accessed by GET .../exam. The result will be:

{"id":3,"name":"OCEJWSD 6"}

Example 1.5. Jersey Client

When using Jersey client to test ExamService, JSONConfiguration .FEATURE_POJO_MAPPING feature should be set to the configuration:

ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
Client client = Client.create(clientConfig);
WebResource resource = client.resource(".../exam");
String result = resource.accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
System.out.print(result);

2. JAXB Support

This approach gives the possibility to quickly and easily produce and consume both JSON and XML data format. A disadvantage of JAXB based approach can appear if it is required to work with a very specific JSON format. A lot of configuration options are provided to solve this problem.

2.1. Simple JAXB Implementation

Example 2.1. JAXB for JSON implementation

Let's start with the simple example of JAXB:

@XmlRootElement
public class Exam {
    public int id;
    public String name;

    public Exam() {}
    public Exam(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Example 2.2. The resource which generates JSON representation

The method of the resource is annotated to produce JSON data format:

@Path("exam")
public class ExamService {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Exam getExam() {
        return new Exam(3, "OCEJWSD 6");
    }
}

The resource can be accessed by GET ../exam. The result is:

{"id":3,"name":"OCEJWSD 6"}

2.2. Use JAXB Annotations

JAXB gives the possibility to change JSON format, for example, to omit some fields or rename others.

Example 2.3. JAXB implementation which omits and rename fields

This example demonstrates how to omit the "id" field and rename the "name" field to "certification":

@XmlRootElement
public class Exam {
    @XmlTransient
    public int id;

    @XmlElement(name="certification")
    public String name;

    public Exam() {}
    public Exam(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Resource from Example 2.2. can be used to test it. The result will be:

{"certification":"OCEJWSD 6"}

2.3. JSON Notations

JSON configuration has four JSON notations. Each notation serializes JSON differently. Here is a list of supported notations:

  • Mapped (default notation)
  • Natural
  • Jettison Mapped
  • Badgerfish

To set JSON notations, it is required to configure the Jersey JSON processor. The next example shows how to do it:

Example 2.4. A JAXBContext resolver implementation

Different configuration options can be set on a JSONConfiguration instance. This instance is used to create a JSONConfigurated JSONJAXBContext, which serves as the main configuration point in this area. Implement a ContextResolver<JAXBContext> to pass specialized JSONJAXBContext to Jersey:

@Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
    private JAXBContext context;
    private Class[] types = {Exam.class};

    public JAXBContextResolver() throws Exception {
        this.context = new JSONJAXBContext(
                JSONConfiguration.natural().build(), types);//1
    }

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

Each notation and its configuration options are described below, using a simple example. The following are JAXB beans and resources, which will be used.

Example 2.5. A simple Exam bean

@XmlRootElement
public class Exam {
    public int id;
    public String name;
    public List<Category> categories;

    public Exam() {
    }

    public Exam(int id, String name, List<Category> categories) {
        this.id = id;
        this.name = name;
        this.categories = categories;
    }
}

Example 2.6. A simple Category bean

@XmlRootElement
public class Category {
    public String name;
    public int questionsNumber;

    public Category() {
    }

    public Category(String name, int questionsNumber) {
        this.name = name;
        this.questionsNumber = questionsNumber;
    }
}

Example 2.7. Resource

@Path("exam")
public class ExamService {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Exam getExam() {
        List<Category> categories = new ArrayList<Category>();
        categories.add(new Category("JAXP",3));
      //  categories.add(new Category("REST",50));
        return new Exam(3, "OCEJWSD 6", categories);
    }
}

2.3.1. Mapped Notation

To set mapped notation to the JSONConfiguration, modify //1 line in Example 2.4 to:

JSONConfiguration.mapped().build()

Then Exam bean will be serialized as:

{"id":"3","name":"OCEJWSD 6","categories":{"name":"JAXP","questionsNumber":"3"}}

One issue might appear if using it with a JavaScript-based client. The information, that the "categories" item represents an array, is being lost for every single element array. Shortly, to satisfy a JavaScript client, it should be:

{"id":"3","name":"OCEJWSD 6","categories":[{"name":"JAXP","questionsNumber":"3"}]}

If you add another "category" bean to the "exam" (uncomment the line in Example 2.7), it works properly.

To fix this issue, you should instruct the JSON processor, what items need to be treated as arrays by setting an optional property, arrays, on your JSONConfiguration object. For our case, it should be:

Example 2.8. Force arrays in mapped JSON notation

JSONConfiguration.mapped().arrays("categories").build()

Items "id" and "questionNumber" are integer numbers, but they are represented as a String in the result. It is possible to change it using another optional property on JSONConfiguration called nonStrings:

Example 2.9. Force non-string values in mapped JSON notation

JSONConfiguration.mapped().arrays("categories").nonStrings("id","questionsNumber").build()

The result will be:

{"id":3,"name":"OCEJWSD 6","categories":[{"name":"JAXP","questionsNumber":3}]}

Notice, that the nonString method accepts multiple string values. The same is true for the "arrays" method as well.

The JAXB beans can have XML attributes:

Example 2.10. JAXB with XML attribute

@XmlRootElement
public class Exam {
    @XmlAttribute
    public int id;
    ...
}

In mapped JSON notation, these attribute names are prefixed with @ character:

{"@id":"3"...

If you want to remove the "@" prefix from the JSON output, use the attributeAsElement configuration option of JSONConfiguration:

Example 2.11. XML attributes as XML elements in mapped JSON notation

JSONConfiguration.mapped().attributeAsElement("id").build()

Mapped JSON notation was designed to produce the simplest possible JSON expression out of JAXB beans. While in XML, you must always have a root tag to start an XML document with, there is no such constraint in JSON. If you want to keep an XML root tag equivalent generated in your JSON, use the rootUnwrapping configuration option:

Example 2.12. Keep XML root tag equivalent in JSON mapped JSON notation

JSONConfiguration.mapped().rootUnwrapping(false).build()

The result will be:

{"exam":{"id":"3","name":"OCEJWSD 6","categories":{"name":"JAXP","questionsNumber":"3"}}}

Mapped JSON notation supports XML namespace.

Example 2.13. JAXB with a namespace

For example, let's look at the Exam class, where the "id" item has declared namespace.

@XmlRootElement
public class Exam {
    @XmlElement(namespace="http://examclouds.com")
    public int id;
   ...
}

To make a JSON object to contain declared namespace, use xml2JsonNs method of JSONConfiguration:

Example 2.14. XML namespace to JSON mapping configuration for mapped notation

Map<String,String> ns2json = new HashMap<String, String>();
ns2json.put("http://examclouds.com", "examclouds");
this.context = new JSONJAXBContext(
     JSONConfiguration.mapped().xml2JsonNs(ns2json).build(), types);

 The result will be:

{"examclouds.id":"3","name":"OCEJWSD 6","categories":{"name":"JAXP","questionsNumber":"3"}}

2.3.2. Natural Notation

After using mapped JSON notation for a while, it was apparent, that a need to configure all the various things manually could be problematic. To avoid the manual work, a new, natural, JSON notation was introduced in Jersey version 1.0.2. With natural notation, Jersey will automatically figure out how individual items need to be processed, so that you do not need to do any kind of manual configuration. Java arrays and lists are mapped into JSON arrays, even for single-element cases. Java numbers and booleans are correctly mapped into JSON numbers and booleans, and you do not need to bother with XML attributes, as in JSON, they keep the original names. So without any additional configuration, you can just use

JSONConfiguration.natural().build()

for configuring JAXBContext. The following JSON will be the result for our Exam bean:

Example 2.15. JSON expression produced using the natural notation

{"id":3,"name":"OCEJWSD 6","categories":[{"name":"JAXP","questionsNumber":3}]}

Notice, that the single element array "categories" remains an array, and also the non-string "id" and "questionsNumber" values are not limited with double quotes, as natural notation automatically detects these things.

To support cases, when inheritance is used for JAXB beans, an option was introduced to the natural JSON configuration builder to forbid XML root element stripping. The option looks pretty the same as at the default mapped notation case (Example 2.12):

Example 2.16. Keep XML root tag equivalent in JSON natural JSON notation

JSONConfiguration.natural().rootUnwrapping(false).build()

2.3.3. Jettison Mapped Notation

This notation is based on the Jettison project. It can be used when working with more complex XML documents for example with multiple XML namespaces in your JAXB beans. Jettison based mapped notation could be configured using:

JSONConfiguration.mappedJettison().build()

The resulting JSON is similar to the default mapped notation. The only difference is, your numbers and booleans will not be converted into strings, but you have no option for forcing arrays to remain arrays in a single-element case. Also, the JSON object, representing the XML root tag is being produced:

Example 2.17. JSON expression produced using Jettison based mapped notation

{"exam":{"id":3,"name":"OCEJWSD 6","categories":{"name":"JAXP","questionsNumber":3}}}

If it is required to work with various XML namespaces, however, you can find Jettison mapped notation very useful. Let's define a particular namespace for the "id" item:

@XmlRootElement
public class Exam {
    @XmlElement(namespace="http://www.examclouds.com")
    public int id;
     ...
}

Then you simply configure a mapping from XML namespace into JSON prefix as follows:

Example 2.18. XML namespace to JSON mapping configuration for Jettison based mapped notation

Map<String,String> ns2json = new HashMap<String, String>();
ns2json.put("http://www.examclouds.com", "examclouds");
this.context = new JSONJAXBContext(
       JSONConfiguration.mappedJettison().xml2JsonNs(ns2json).build(), types);

The resulting JSON will look like this:

{"exam":{"examclouds.id":3,
          "name":"OCEJWSD 6",
          "categories":{"name":"JAXP","questionsNumber":3}}}

Note, that the "id" item became examclouds.id based on the XML namespace mapping. If you have more XML namespaces in your XML, you will need to configure appropriate mapping for all of them.

2.3.4. Badgerfish Notation

Badgerfish notation is the other notation based on Jettison. From JSON and JavaScript perspective, this notation is definitely the worst readable one. You will probably not want to use it, unless you need to make sure your JAXB beans could be flawlessly written and read back to and from JSON, without bothering with any formatting configuration, namespaces, etc.

JSONConfiguration instance using the badgerfish notation could be built with:

JSONConfiguration.badgerFish().build()

The output JSON will be:

{"exam":{"id":{"$":"3"},
         "name":{"$":"OCEJWSD 6"},
         "categories":{"name":{"$":"JAXP"},"questionsNumber":{"$":"3"}}}}

3. Low-Level JSON Support

JSONObject and JSONArray classes taken from the Jettison project are used for data representations in this approach. The biggest advantage here is, that you will gain full control over the JSON format produced and consumed. On the other hand, dealing with your data model objects will probably be a bit more complex, than when taking the JAXB based approach. 

Example 3.1. JAXB bean creation

The example shows how to construct a simple Exam JAXB bean:

List<Category> categories = new ArrayList<Category>();
categories.add(new Category("JAXP",3));
Exam exam = Exam(3, "OCEJWSD 6", categories);

The example produces JSON:

{"id":3,"name":"OCEJWSD 6","categories":[{"name":"JAXP","questionsNumber":3}]}

To build an equivalent JSONObject, you will need to write more lines of code:

Example 3.2. Constructing a JSONObject

JSONObject exam = new JSONObject();
JSONObject category = new JSONObject();
JSONArray categories = new JSONArray();
try {
     category.put("name", "JAXP");
     category.put("questionsNumber", 3);
     categories.put(category);

     exam.put("id", 3);
     exam.put("name", "OCEJWSD 6");
     exam.put("categories", categories);
} catch (JSONException e) {
     System.out.print(e);
}
Курс 'Java для начинающих' на Udemy Курс 'Java для начинающих' на Udemy
Read also:
Comments