2010-12-13 21 views
36

Estoy usando Jersey + Jackson para proporcionar la capa de servicios REST JSON para mi aplicación. El problema que tengo es que el formato de serialización por defecto la fecha parece que:Jersey + Jackson JSON serialización de formato de fecha - cómo cambiar el formato o usar JacksonJsonProvider personalizado

"CreationDate":1292236718456 

Al principio pensé que se trata de una marca de tiempo UNIX ... pero es demasiado tiempo para eso. Mi biblioteca JS del lado del cliente tiene problemas para deserializar este formato (admite varios formatos de fecha diferentes, pero no este, supongo). Quiero cambiar el formato para que pueda ser consumido por mi biblioteca (a ISO, por ejemplo). ¿Cómo hago eso? Encontré un fragmento de código que podría ayudar pero ... ¿dónde lo pongo ya que no controlo la instanciación del serializador Jackson (lo hace Jersey)?

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

También encontré este código de encargo JacksonJsonProvider - la pregunta es .. ¿cómo hago todas mis clases POJO lo utilizan?

@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; 
     } 
    } 
} 
+0

He encontrado que esto es un cambio entre New Jersey 1.1.5 y 1.6 - Jersey con 1.1.5, la serialización JSON se parece a esto: { "fecha": "2011-04-01T16: 41: 18.707 + 00: 00 "} - tal vez haya un elemento de seguimiento de problemas que proporcione información básica – mjn

+0

sí, es la plomería y la configuración la parte que consume más tiempo. Todavía no me he dado cuenta. Sugiero solo sacar fechas como una cadena, y luego dejar que su cliente se encargue de convertir cadenas en fechas. Se trata de sustituir el código de configuración. Preferiría escribir código que esté 'dentro de mi control' en lugar de misterios de configuración que sé que puedo controlar, pero que a partir de ahora parecen fuera de mi control. Las configuraciones son complicadas sin ejemplos explícitos que, por lo tanto, usan las mismas piezas del kit de herramientas java que está utilizando. La portabilidad es un objetivo digno, pero realmente difícil. – user798719

Respuesta

6

Por lo que vale la pena, que es el número de marca de tiempo estándar de Java (JDK utilizada por clases); Unix almacena segundos, milisegundos de Java, por lo que su valor es un poco mayor.

Espero que haya algunos documentos sobre cómo inyectar ObjectMapper en Jersey (debe seguir la forma habitual de inyectar el objeto provisto). Pero alternativamente, usted puede anular JacksonJaxRsProvider para especificar/configurar ObjectMapper y registrar eso; esto es lo que hace el propio Jersey, y hay múltiples formas de hacerlo.

+4

Muchas gracias por la pista. El problema es que no puedo encontrar ningún documento sobre cómo hacer esto. – adrin

14

para configurar su propia ObjectMapper, es necesario inyectar su propia clase que implementa ContextResolver <ObjectMapper>

exactamente cómo llegar Jersey para recoger esto va a clase de depender de su COI (primavera, guice). Yo uso la primavera, y mi clase es como la siguiente:

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; 
    } 
} 
30

logré hacerlo en Resteasy "el camino de JAX-RS", por lo que debería funcionar en todas las implementaciones compatibles como Jersey (recientemente probado con éxito en JEE7 servidor Wildfly 8, solo requirió algunos cambios en la parte de Jackson porque cambiaron algunas API).

Debe definir un ContextResolver (compruebe que produce contiene el correcto tipo de contenido):

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; 
    } 

} 

a continuación, debe devolver la clase recién creada en getClasses de su javax.ws.rs.core.Application

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; 
     } 

} 

de esta manera, todas las operaciones realizadas a través de jackson reciben el ObjectMapper de su elección.

EDIT: recientemente descubrí a mis expensas que usar RestEasy 2.0.1 (y por lo tanto Jackson 1.5.3) existe un comportamiento extraño si decide extender el JacksonConfigurator para agregar asignaciones personalizadas.

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

Si usted acaba de hacer así (y por supuesto poner la clase extendida en RestApplication) el asignador de la clase padre se utiliza, es decir a perder las asignaciones personalizadas.Para hacerlo correctamente trabajo que tenía que hacer algo que parece inútil para mí de otra manera:

public class MyJacksonConfigurator extends JacksonConfigurator implements ContextResolver<ObjectMapper> 
+0

1 por no requerir la inyección de dependencias – Mark

+10

SimpleDateFormat no es adecuado para entornos multiproceso (que he visto este produzco insectos desagradables). ¿Alguien sabe cómo la configuración usa SimpleDataFormat? –

+10

Sí, lo sé (aunque siempre me sorprende ver cuántos desarrolladores "experimentados" no lo saben); Busqué tanto en los documentos como en el código, y la instancia proporcionada se usa solo como un modelo, siempre se clona antes de su uso. No estoy seguro que es una gran elección de los desarrolladores de Jackson, pero su API requiere un DateFormat, que no se garantiza que sea multi-hilo, y tener esto en cuenta mediante la clonación de ellos. Gracias por su buen comentario de todos modos. –

-1

Re-escribir el MessageBodyWriterJSON con este

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; 
     } 
} 
+1

¿Entonces qué? ¿Cómo haces que Jersey use esto? –

0

Si decide trabajar con Joda DateTime objetos en el servidor y desea serializar a ISO8601 puede usar Jackson's JodaModule. Puede registrar un proveedor de Jersey de la siguiente manera:

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; 
    } 
} 

Más información disponible en Jersey's website.

+0

Esta es la versión FasterXML de arriba. –

-1

JSON-io (https://github.com/jdereg/json-io) es una aplicación Java completa a/desde la biblioteca de serialización JSON. Cuando se utiliza para escribir la cadena JSON, se puede establecer cómo se formatean fechas. Por defecto, las fechas se escriben siempre (como arriba, que es milisegundos desde el 1 de enero de 1970). Sin embargo, puede darle un formato String o Java DateFormatter y tener las fechas escritas en el formato que desee.

1

que tenían el mismo problema (utilizando Jackson Jersey + + JSON), el cliente estaba enviando una fecha, pero se estaba cambiando en el servidor cuando los datos se han mapeado en el objeto.

me siguió otro enfoque para resolver esto, mediante la lectura de este enlace: http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html, cuando me di cuenta de que la fecha de recepción era una marca de tiempo (el mismo que Adrin en su pregunta: "creationDate":1292236718456)

En mi clase VO añadí esta anotación al atributo @XmlJavaTypeAdapter y también implementó un wich clase interna extendió 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; 
     } 
} 

espero que podría ayudar a que usted también.

Cuestiones relacionadas