Tuesday, June 26, 2012

Making Jersey to use Jackson instead of JAXB (server and client side)


Background


Jersey is a JAX-RS implementation which enables us writing REST resources by annotating interfaces/implementation in server side. Both XML and JSON data interchange format is supported by jersey. 
It uses JAXB for xml data interchange and JAXB again (with a JSON parser) for JSON interchange. JSON that's produced follows a specific notation - its JAXB based and can come with it's own issues:

a. array structure not formed in resultant JSON for single element arrays
e.g. {names : "Harry"} rather than {names : ["Harry"]}
b. exact enum name not used - name is taken from JAXB XML annotation

public enum AckValue {

    @XmlEnumValue("Success")
    SUCCESS("Success"),
    @XmlEnumValue("Failure")
    FAILURE("Failure")

produces {ack : "Success"} rather than {ack : "SUCCESS"}

These impose problems upon de-serializing JSON. Easy solution is to ask Jersey to use Jackson, an open-source, famous, so-far-best JSON parser (my opinion) for serialization which solves all stated issues above. But its not that easy.. 

Server side


Have Jackson jar (alone) in classpath


Make sure you have Jackson jar in classpath along with Jersey. Also for safety reasons, make sure any other JSON parser is not present, for e.g. Jettison. With multiple JSON parsers present in classpath, we can't guarantee that Jersey will pick Jackson. And with Jettison, not both of above issues can be resolved. For sample, at the time of writing, Jettison returns arrays with single element not enclosed in [] (issue 1).

Enable JSON POJO mapping

There are several JSON annotations that are available. POJO notation is one such which is vanilla POJO -> JSON -> POJO way and to use this we need to enable JSON POJO mapping (as mentioned in link above):

         <init-param>
            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            <param-value>true</param-value>
         </init-param>

Even with this, Jersey still might use JAXB for JSON serialization! Read further..

Jackson JAX-RS provider 

We might need to inject a custom provider to make Jersey use Jackson. Fortunately, we don't need to write one as one is already present in jackson-jaxrs jar (hence add this as dependency). Now to ask Jersey to use it, add the package "org.codehaus.jackson.jaxrs" to scan for it:

        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>org.codehaus.jackson.jaxrs;<your other packages with resources>   </param-value>
        </init-param>

Reference : SO post



Client side

Per POJO notation, create client this way

 ClientConfig clientConfig = new DefaultClientConfig();
 clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
 // force client to use Jackson JAX-RS provider (one in org.codehaus.jackson.jaxrs)
 // without it i faced issues with Jersey unable to locate my custom resolver with custom serialization/deserialization logic, in my client side
 // see this http://crazytechbuddy.blogspot.com/2012/06/jackson-custom-serializationdeserializa.html
 clientConfig.getClasses().add(JacksonJsonProvider.class);
 Client client = Client.create(clientConfig);
 
and specify the "Content-type" as "application/json" in header 
 
 <Your response class> response = resource.header("Content-Type", MediaType.APPLICATION_JSON).
                                           accept(MediaType.APPLICATION_JSON).get(<Your response class>.class);
 
That's it, now Jersey will use Jackson in both serialization and de-serialization, in both server side and client side. 

1 comment:

  1. Thanks, this was just what I needed.
    When using server side, is there a way to tell jersey to use jackson just on specific methods ?

    ReplyDelete