Table of Contents
Jersey JSON support comes as a set of JAX-RS MessageBodyReader<T> and MessageBodyWriter<T> providers distributed with jersey-json module. These providers enable using three basic approaches when working with JSON format:
The first method is pretty generic and allows you to map any Java Object to JSON and vice versa. The other two approaches limit you in Java types your resource methods could produce and/or consume. JAXB based approach could be taken if you want to utilize certain JAXB features. The last, low-level, approach gives you the best fine-grained control over the outcoming JSON data format.
POJO suppport represents the easiest way to convert your Java Objects to JSON and back. It is based on the Jackson library.
To use this approach, you will need to turn the JSONConfiguration.FEATURE_POJO_MAPPING feature on.
This could be done in web.xml
using the following servlet init parameter:
Example 5.1. POJO JSON support web.xml configuration
1 <init-param> 2 <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> 3 <param-value>true</param-value> 4 </init-param>
The following snippet shows how to use the POJO mapping feature on the client side:
Example 5.2. POJO JSON support client configuration
1 ClientConfig clientConfig = new DefaultClientConfig(); 2 clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE); 3 Client client = Client.create(clientConfig);
Jackson JSON processor could be futher controlled via providing custom Jackson ObjectMapper instance. This could be handy if you need to redefine the default Jackson behaviour and to fine-tune how your JSON data structures look like. Detailed description of all Jackson features is out of scope of this guide. The example bellow gives you a hint on how to wire your ObjectMapper instance into your Jersey application.
Download https://maven.java.net/service/local/artifact/maven/redirect?r=releases&g=com.sun.jersey.samples&a=jacksonjsonprovider&v=1.19.1&c=project&e=zip to get a complete example using POJO based JSON support.
Taking this approach will save you a lot of time, if you want to easily produce/consume both JSON and XML data format. Because even then you will still be able to use a unified Java model. Another advantage is simplicity of working with such a model, as JAXB leverages annotated POJOs and these could be handled as simple Java beans.
A disadvantage of JAXB based approach could be if you need to work with a very specific JSON format. Then it could be difficult to find a proper way to get such a format produced and consumed. This is a reason why a lot of configuration options are provided, so that you can control how things get serialized out and deserialized back.
Following is a very simple example of how a JAXB bean could look like.
Example 5.3. Simple JAXB bean implementation
1 @XmlRootElement 2 public class MyJaxbBean { 3 public String name; 4 public int age; 5 6 public MyJaxbBean() {} // JAXB needs this 7 8 public MyJaxbBean(String name, int age) { 9 this.name = name; 10 this.age = age; 11 } 12 }
Using the above JAXB bean for producing JSON data format from you resource method, is then as simple as:
Example 5.4. JAXB bean used to generate JSON representation
1 @GET @Produces("application/json") 2 public MyJaxbBean getMyBean() { 3 return new MyJaxbBean("Agamemnon", 32); 4 }
Notice, that JSON specific mime type is specified in @Produces annotation, and the method returns an instance
of MyJaxbBean, which JAXB is able to process. Resulting JSON in this case would look like:
{"name":"Agamemnon", "age":"32"}
JAXB itself enables you to control output JSON format to certain extent. Specifically renaming
and ommiting items is easy to do directly using JAXB annotations.
E.g. the following example depicts changes in the above mentioned MyJaxbBean that will result in {"king":"Agamemnon"}
JSON output.
Example 5.5. Tweaking JSON format using JAXB
1 @XmlRootElement 2 public class MyJaxbBean { 3 4 @XmlElement(name="king") 5 public String name; 6 7 @XmlTransient 8 public int age; 9 10 // several lines removed 11 }
To achieve more important JSON format changes, you will need to configure Jersey JSON procesor itself. Various configuration options could be set on an JSONConfiguration instance. The instance could be then further used to create a JSONConfigurated JSONJAXBContext, which serves as a main configuration point in this area. To pass your specialized JSONJAXBContext to Jersey, you will finally need to implement a JAXBContext ContextResolver<T>:
Example 5.6. An example of a JAXBContext resolver implementation
1 @Provider 2 public class JAXBContextResolver implements ContextResolver<JAXBContext> { 3 4 private JAXBContext context; 5 private Class[] types = {MyJaxbBean.class}; 6 7 public JAXBContextResolver() throws Exception { 8 this.context = 9 new JSONJAXBContext( (1) 10 JSONConfiguration.natural().build(), types); (2) 11 } 12 13 public JAXBContext getContext(Class<?> objectType) { 14 for (Class type : types) { 15 if (type == objectType) { 16 return context; 17 } 18 } 19 return null; 20 } 21 }
JSONConfiguration allows you to use four various JSON notations. Each of these notations serializes JSON in a different way. Following is a list of supported notations:
MAPPED (default notation)
NATURAL
JETTISON_MAPPED
BADGERFISH
Individual notations and their further configuration options are described bellow. Rather then explaining rules for mapping XML constructs into JSON, the notations will be described using a simple example. Following are JAXB beans, which will be used.
Example 5.7. JAXB beans for JSON supported notations description, simple address bean
1 @XmlRootElement 2 public class Address { 3 public String street; 4 public String town; 5 6 public Address(){} 7 8 public Address(String street, String town) { 9 this.street = street; 10 this.town = town; 11 } 12 }
Example 5.8. JAXB beans for JSON supported notations description, contact bean
1 @XmlRootElement 2 public class Contact { 3 4 public int id; 5 public String name; 6 public List<Address> addresses; 7 8 public Contact() {}; 9 10 public Contact(int id, String name, List<Address> addresses) { 11 this.name = name; 12 this.id = id; 13 this.addresses = 14 (addresses != null) ? new LinkedList<Address>(addresses) : null; 15 } 16 }
Following text will be mainly working with a contact bean initialized with:
Example 5.9. JAXB beans for JSON supported notations description, initialization
final Address[] addresses = {new Address("Long Street 1", "Short Village")}; Contact contact = new Contact(2, "Bob", Arrays.asList(addresses));
I.e. contact bean with id=2
, name="Bob"
containing
a single address (street="Long Street 1"
, town="Short Village"
).
All bellow described configuration options are documented also in apidocs at http://jersey.java.net/nonav/apidocs/1.19.1/jersey/com/sun/jersey/api/json/JSONConfiguration.html
JSONConfiguration
based on mapped
notation could be build with
JSONConfiguration.mapped().build()
for usage in a JAXBContext
resolver, Example 5.6, “An example of a JAXBContext resolver implementation”.
Then a contact bean initialized with Example 5.9, “JAXB beans for JSON supported notations description, initialization”, will be serialized as
Example 5.10. JSON expression produced using mapped
notation
1 { "id":"2" 2 ,"name":"Bob" 3 ,"addresses":{"street":"Long Street 1" 4 ,"town":"Short Village"}}
The JSON representation seems fine, and will be working flawlessly with Java based Jersey client API.
However, at least one issue might appear once you start using it with a JavaScript based client.
The information, that addresses
item represents an array, is being lost for every single element array.
If you added another address bean to the contact,
contact.addresses.add(new Address("Short Street 1000", "Long Village"));
, you would get
1 { "id":"2" 2 ,"name":"Bob" 3 ,"addresses":[{"street":"Long Street 1","town":"Short Village"} 4 ,{"street":"Short Street 1000","town":"Long Village"}]}
Both representations are correct, but you will not be able to consume them using a single JavaScript client,
because to access "Short Village"
value, you will write addresses.town
in one
case and addresses[0].town
in the other.
To fix this issue, you need to 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, you would do it with
Example 5.11. Force arrays in mapped
JSON notation
JSONConfiguration.mapped().arrays("addresses").build()
You can use multiple string values in the arrays
method call, in case you are dealing with more
than one array item in your beans. Similar mechanism (one or more argument values) applies also for all below desribed options.
Another issue might be, that number value, 2
, for id
item
gets written as a string, "2"
. To avoid this, you can use another optional property on JSONConfiguration
called nonStrings
.
Example 5.12. Force non-string values in mapped
JSON notation
JSONConfiguration.mapped().arrays("addresses").nonStrings("id").build()
It might happen you use XML attributes in your JAXB beans. In mapped
JSON notation, these attribute names are prefixed with @
character. If id
was an attribute, it´s definition would look like:
... @XmlAttribute public int id; ...
and then you would get
{"@id":"2" ...
at the JSON output.
In case, you want to get rid of the @
prefix, you can take advantage of another configuration
option of JSONConfiguration
, called attributeAsElement
.
Usage is similar to previous options.
Example 5.13. 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 a XML document with, there is no such a constraint in JSON. If you wanted to be strict,
you might have wanted to keep a XML root tag equivalent generated in your JSON. If that is the case, another configuration option
is available for you, which is called rootUnwrapping
. You can use it as follows:
Example 5.14. Keep XML root tag equivalent in JSON mapped
JSON notation
JSONConfiguration.mapped().rootUnwrapping(false).build()
and get the following JSON for our Contact
bean:
Example 5.15. XML root tag equivalent kept in JSON using mapped
notation
1 {"contact":{ "id":"2" 2 ,"name":"Bob" 3 ,"addresses":{"street":"Long Street 1" 4 ,"town":"Short Village"}}}
rootUnwrapping
option is set to true
by default. You should switch it to false
if you use inheritance at your JAXB beans. Then JAXB might try to encode type information into root element names, and by stripping these
elements off, you could break unmarshalling.
In version 1.1.1-ea, XML namespace support was added to the MAPPED JSON notation. There is of course no such thing as XML namespaces in JSON, but when working from JAXB, XML infoset is used as an intermediary format. And then when various XML namespaces are used, ceratin information related to the concrete namespaces is needed even in JSON data, so that the JSON procesor could correctly unmarshal JSON to XML and JAXB. To make it short, the XML namespace support means, you should be able to use the very same JAXB beans for XML and JSON even if XML namespaces are involved.
Namespace mapping definition is similar to Example 5.20, “XML namespace to JSON mapping configuration for Jettison based mapped
notation”
Example 5.16. XML namespace to JSON mapping configuration for mapped
notation
1 Map<String,String> ns2json = new HashMap<String, String>(); 2 ns2json.put("http://example.com", "example"); 3 context = new JSONJAXBContext( 4 JSONConfiguration.mapped() 5 .xml2JsonNs(ns2json).build(), types);
Dot character (.) will be used by default as a namespace separator in the JSON identifiers. E.g. for the above mentioned example
namespace
and tag T
, "example.T"
JSON identifier will be generated. To change this default behaviour, you can use the nsSeparator
method on the mapped JSONConfiguration builder: JSONConfiguration.mapped().xml2JsonNs(ns2json).nsSeparator(':').build()
. Then you will
get "example:T"
instead of "example.T"
generated. This option should be used carefully, as the Jersey framework does not even try to check
conflicts between the user selected separator character and the tag and/or namespace names.
After using mapped
JSON notation for a while, it was apparent, that a need to configure all the various things
manually could be a bit 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, just using
JSONConfiguration.natural().build()
for configuring your JAXBContext
, you will get the following JSON for the bean
initialized at Example 5.9, “JAXB beans for JSON supported notations description, initialization”:
Example 5.17. JSON expression produced using natural
notation
1 { "id":2 2 ,"name":"Bob" 3 ,"addresses":[{"street":"Long Street 1" 4 ,"town":"Short Village"}]}
You might notice, that the single element array addresses
remains an array, and also the non-string id
value is not limited with double quotes, as natural
notation automatically detects these things.
To support cases, when you use inheritance for your JAXB beans, an option was introduced to the natural
JSON configuration builder
to forbid XML root element stripping. The option looks pretty same as at the default mapped
notation case (Example 5.14, “Keep XML root tag equivalent in JSON mapped
JSON notation”).
Example 5.18. Keep XML root tag equivalent in JSON natural
JSON notation
JSONConfiguration.natural().rootUnwrapping(false).build()
Next two notations are based on project Jettison. You might want to use one of these notations, when working with more complex XML documents. Namely when you deal with multiple XML namespaces in your JAXB beans.
Jettison based mapped
notation could be configured using:
JSONConfiguration.mappedJettison().build()
If nothing else is configured, you will get similar JSON output as for the default, mapped
, notation:
Example 5.19. JSON expression produced using Jettison based mapped
notation
1 { "contact:{"id":2 2 ,"name":"Bob" 3 ,"addresses":{"street":"Long Street 1" 4 ,"town":"Short Village"}}
The only difference is, your numbers and booleans will not be converted into strings, but you have no option for forcing arrays remain arrays
in single-element case. Also the JSON object, representing XML root tag is being produced.
If you need to deal with various XML namespaces, however, you will find Jettison mapped
notation pretty useful.
Lets define a particular namespace for id
item:
... @XmlElement(namespace="http://example.com") public int id; ...
Then you simply confgure a mapping from XML namespace into JSON prefix as follows:
Example 5.20. XML namespace to JSON mapping configuration for Jettison based mapped
notation
1 Map<String,String> ns2json = new HashMap<String, String>(); 2 ns2json.put("http://example.com", "example"); 3 context = new JSONJAXBContext( 4 JSONConfiguration.mappedJettison() 5 .xml2JsonNs(ns2json).build(), types);
Resulting JSON will look like in the example bellow.
Example 5.21. JSON expression with XML namespaces mapped into JSON
1 { "contact:{"example.id":2 2 ,"name":"Bob" 3 ,"addresses":{"street":"Long Street 1" 4 ,"town":"Short Village"}}
Please note, that id
item became example.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
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 badgerfish
notation could be built with
JSONConfiguration.badgerFish().build()
and the output JSON for Example 5.9, “JAXB beans for JSON supported notations description, initialization” will be as follows.
Example 5.22. JSON expression produced using badgerfish
notation
1 {"contact":{"id":{"$":"2"} 2 ,"name":{"$":"Bob"} 3 ,"addresses":{"street":{"$":"Long Street 1"} 4 ,"town":{"$":"Short Village"}}}}
Download https://maven.java.net/service/local/artifact/maven/redirect?r=releases&g=com.sun.jersey.samples&a=json-from-jaxb&v=1.19.1&c=project&e=zip or https://maven.java.net/service/local/artifact/maven/redirect?r=releases&g=com.sun.jersey.samples&a=jmaki-backend&v=1.19.1&c=project&e=zip to get a more complex example using JAXB based JSON support.
Using this approach means you will be using JSONObject and/or JSONArray classes for your data representations. These classes are actually taken from Jettison project, but conform to the description provided at http://www.json.org/java/index.html.
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. Differencies are depicted at the following code snipets.
Above you construct a simple JAXB bean, which could be written in JSON as {"name":"Agamemnon", "age":32}
Now to build an equivalent JSONObject (in terms of resulting JSON expression), you would need several more lines of code.
Example 5.24. Constructing a JSONObject
1 JSONObject myObject = new JSONObject(); 2 myObject.JSONObject myObject = new JSONObject(); 3 try { 4 myObject.put("name", "Agamemnon"); 5 myObject.put("age", 32); 6 } catch (JSONException ex) { 7 LOGGER.log(Level.SEVERE, "Error ...", ex); 8 }
Download https://maven.java.net/service/local/artifact/maven/redirect?r=releases&g=com.sun.jersey.samples&a=bookmark&v=1.19.1&c=project&e=zip to get a more complex example using low-level JSON support.