Wednesday, June 27, 2012

Jackson custom serialization/deserialization (for java.util.Date) in Jersey

Approach #1


Write custom serializer and deserializer


import java.io.IOException;
import java.util.Date;

import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;

/**
 * Used to serialize Java.util.Date, which is not a common JSON
 * type, so we have to create a custom serialize method;.
 *
 */
public class JsonDateSerializer extends JsonSerializer<Date>{

    @Override
    public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
            throws IOException, JsonProcessingException {
        String formattedDate = DateAdapter.printDate(date);
        gen.writeString(formattedDate);
    }
   
}


 import java.io.IOException;
import java.util.Date;

import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;

public class JsonDateDeSerializer extends JsonDeserializer<Date>{

    @Override
    public Date deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        System.out.println("JSON deserialization for " + jp.getText());
        return DateAdapter.parseDate(jp.getText());
    }

  
}

Annotate  each date field


Where ever date is declared, you annotate with following Jackson annotation specifying the serializer/deserializer to use

   @JsonSerialize(using=JsonDateSerializer.class, as = Date.class)
    public Date getCreatedOn() {
        return createdOn;
    }

    /**
     * Sets the value of the createdOn property.
     *
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    @JsonDeserialize(using=JsonDateDeSerializer.class, as = Date.class)
    public void setCreatedOn(Date value) {
        this.createdOn = value;
    }

Note:

If you have getter/setters, annotations should NOT be given in fields but in getter/setter. See this.


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