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.



Approach #2


The problem with above approach is you have to annotate each and every field having type Date. Sometimes you won't have access to code or have just the effect to be applied globally for all Date types. If so, you can define a custom ContextResolver and have Jackson use it. 

package com.samples.resolvers;

import javax.ws.rs.Produces;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;

@Provider
@Produces("application/json")
public class JacksonConfigurator implements ContextResolver<ObjectMapper> {
    public final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
            "yyyy-MM-dd'T'HH:mm:ss.SSSZ");

    private ObjectMapper mapper = new ObjectMapper();

    public JacksonConfigurator() {
        SerializationConfig serConfig = mapper.getSerializationConfig();
        serConfig.setDateFormat(DATE_FORMAT);
        DeserializationConfig deserializationConfig = mapper.getDeserializationConfig();
        deserializationConfig.setDateFormat(DATE_FORMAT);
        mapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    @Override
    public ObjectMapper getContext(Class<?> arg0) {
        return mapper;
    }

}

and provide the package of this class for Jersey to scan and locate:

        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>com.samples.resolvers;<other pkgs with resources etc.></param-value>
        </init-param>
Reference: See this, this, this

1 comment: