Jersey + Jackson JSON date format serialization - how to change the format or use custom JacksonJsonProvider

StackOverflow https://stackoverflow.com/questions/4428109

Pergunta

I am using Jersey + Jackson to provide REST JSON services layer for my application. The problem I have is that the default Date serialization format looks like that:

"CreationDate":1292236718456

At first I thought it is a UNIX timestamp... but it is too long for that. My client-side JS library has problems deserializing this format (it supports a bunch of different date formats but not this one I suppose). I want to change the format so that it can be consumable by my library (to ISO for example). How do I do that... I found a piece of code that could help but... where do I put it as I don't control the Jackson serializer instantiation (Jersey does)?

objectMapper.configure(
    SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);

I also found this code for custom JacksonJsonProvider - the question is .. how do I make all my POJO classes use it?

@Provider
public class MessageBodyWriterJSON extends JacksonJsonProvider {

    private static final String DF = "yyyy-MM-dd’T'HH:mm:ss.SSSZ";

    @Override
    public boolean isWriteable(Class arg0, Type arg1, Annotation[] arg2,
            MediaType arg3) {
        return super.isWriteable(arg0, arg1, arg2,
                arg3);
    }
    @Override
    public void writeTo(Object target, Class arg1, Type arg2, Annotation[] arg3,
            MediaType arg4, MultivaluedMap arg5, OutputStream outputStream)
            throws IOException, WebApplicationException {
            SimpleDateFormat sdf=new SimpleDateFormat(DF);

        ObjectMapper om = new ObjectMapper();
        om.getDeserializationConfig().setDateFormat(sdf);
        om.getSerializationConfig().setDateFormat(sdf);
        try {
            om.writeValue(outputStream, target);
        } catch (JsonGenerationException e) {
            throw e;
        } catch (JsonMappingException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        }
    }
}
Foi útil?

Solução

For what it's worth, that number is standard Java timestamp (used by JDK classes); Unix stores seconds, Java milliseconds, which is why it's bit larger value.

I would hope there are some documents as to how to inject ObjectMapper into Jersey (it should follow the usual way to inject provided object). But alternatively you could override JacksonJaxRsProvider to specify/configure ObjectMapper and register that; this is what Jersey itself does, and there are multiple ways to do it.

Outras dicas

I managed to do it in Resteasy "the JAX-RS way", so it should work on every compliant implementation like Jersey (recently successfully tested on JEE7 server Wildfly 8, it just required a few changes to the Jackson part because they changed a few APIs).

You must define a ContextResolver (check that Produces contains the correct content-type):

import javax.ws.rs.ext.ContextResolver;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.DeserializationConfig;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.Produces; 
import java.text.SimpleDateFormat;
@Provider
@Produces("application/json")
public class JacksonConfigurator implements ContextResolver<ObjectMapper> {

    private ObjectMapper mapper = new ObjectMapper();

    public JacksonConfigurator() {
        SerializationConfig serConfig = mapper.getSerializationConfig();
        serConfig.setDateFormat(new SimpleDateFormat(<my format>));
        DeserializationConfig deserializationConfig = mapper.getDeserializationConfig();
        deserializationConfig.setDateFormat(new SimpleDateFormat(<my format>));
        mapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

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

}

Then you must return the newly created class in your javax.ws.rs.core.Application's getClasses

import javax.ws.rs.core.Application;
public class RestApplication extends Application {

     @Override
     public Set<Class<?>> getClasses() {
         Set<Class<?>> classes = new HashSet<Class<?>>();
         // your classes here
         classes.add(JacksonConfigurator.class);
         return classes;
      }

}

this way all operation made through jackson are given the ObjectMapper of your choice.

EDIT: I recently found out at my expenses that using RestEasy 2.0.1 (and thus Jackson 1.5.3) there is a strange behaviour if you decide to extend the JacksonConfigurator to add custom mappings.

import javax.ws.rs.core.MediaType;
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class MyJacksonConfigurator extends JacksonConfigurator

If you just do like this (and of course put the extended class in RestApplication) the mapper of the parent class is used, that is you lose the custom mappings. To make it correctly work I had to do something that seems useless to me otherwise:

public class MyJacksonConfigurator extends JacksonConfigurator implements ContextResolver<ObjectMapper> 

To configure your own ObjectMapper, you need to inject your own class that implements ContextResolver<ObjectMapper>

Exactly how to get jersey to pick this up will kind of depend on your IOC (spring, guice). I use spring, and my class looks something like this:

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

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig.Feature;
import org.codehaus.jackson.map.deser.CustomDeserializerFactory;
import org.codehaus.jackson.map.deser.StdDeserializerProvider;
import org.codehaus.jackson.map.ser.CustomSerializerFactory;
import org.springframework.stereotype.Component;

// tell spring to look for this.
@Component
// tell spring it's a provider (type is determined by the implements)
@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
    @Override
    public ObjectMapper getContext(Class<?> type) {
        // create the objectMapper.
        ObjectMapper objectMapper = new ObjectMapper();
        // configure the object mapper here, eg.
           objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
        return objectMapper;
    }
}

If you choose to work with Joda DateTime objects on your server and want to serialize to ISO8601 you could use Jackson's JodaModule. You can register a Jersey Provider as follows:

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

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;

@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {

  final ObjectMapper objectMapper;

  public MyObjectMapperProvider() {
    objectMapper = new ObjectMapper();
    /* Register JodaModule to handle Joda DateTime Objects. */
    objectMapper.registerModule(new JodaModule());
    /* We want dates to be treated as ISO8601 not timestamps. */
    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
  }

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

More information available on Jersey's website.

I had the same problem (using Jersey+Jackson+Json), the client was sending a date, but it was being changed in the server when the data was mapped into the object.

I followed other approach to solve this, by reading this link: http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html, when I realized that the date received was a TimeStamp (the same as Adrin in his question: "creationDate":1292236718456)

In my VO class I added this annotation to the attribute @XmlJavaTypeAdapter and also implemented an inner class wich extended XmlAdapter:

@XmlRootElement
public class MyClassVO {
   ...
   @XmlJavaTypeAdapter(DateFormatterAdapter.class) 
   Date creationDate;
   ...

   private static class DateFormatterAdapter extends XmlAdapter<String, Date> {
      @Override
      public Date unmarshal(final String v) throws Exception {
         Timestamp stamp = new Timestamp(new Long(v));
         Date date = new Date(stamp.getTime());
         return date;
      }
}

I hope it could help to you also.

Below code worked for me - JAX-RS 1.1, Jersy 1.8

import java.text.SimpleDateFormat;

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;

import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;


@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JsonProvider extends JacksonJaxbJsonProvider {
  private static final ObjectMapper objectMapper = new ObjectMapper();
  static {
    // allow only non-null fields to be serialized
    objectMapper.getSerializationConfig().setSerializationInclusion(Inclusion.NON_NULL);

    SerializationConfig serConfig = objectMapper.getSerializationConfig();
    serConfig.setDateFormat(new SimpleDateFormat(<your date format>));
    DeserializationConfig deserializationConfig = objectMapper.getDeserializationConfig();
    deserializationConfig.setDateFormat(new SimpleDateFormat(<your date format>));
    objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);

  }

  public JsonProvider() {
    super.setMapper(objectMapper);
  }
}

Re-write the MessageBodyWriterJSON with this

import javax.ws.rs.core.MediaType; 
import javax.ws.rs.ext.Provider; 

import org.codehaus.jackson.jaxrs.JacksonJsonProvider; 
import org.codehaus.jackson.map.ObjectMapper; 
import org.codehaus.jackson.map.SerializationConfig; 

@Provider 
public class MessageBodyWriterJSON extends JacksonJsonProvider { 
            public MessageBodyWriterJSON (){ 
            } 

        @Override 
            public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) 
        { 
        ObjectMapper mapper = super.locateMapper(type, mediaType); 
        //DateTime in ISO format "2012-04-07T17:00:00.000+0000" instead of 'long' format 
            mapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false); 
            return mapper; 
        } 
}

json-io (https://github.com/jdereg/json-io) is a complete Java to / from JSON serialization library. When using it to write the JSON string, you can set how dates are formatted. By default, dates are written out as long (as above, which is milliseconds since Jan 1, 1970). However, you can give it a format String or Java DateFormatter and have the dates written in whatever format you wish.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top