web-dev-qa-db-fra.com

Sérialisation du format de date Jersey + Jackson JSON - comment changer le format ou utiliser un JacksonJsonProvider personnalisé

J'utilise Jersey + Jackson pour fournir la couche de services REST JSON pour mon application. Le problème que j'ai est que le format de sérialisation de date par défaut ressemble à ceci:

"CreationDate":1292236718456

Au début, je pensais que c'était un horodatage UNIX ... mais c'est trop long pour ça. Ma bibliothèque JS côté client a des problèmes de désérialisation de ce format (elle prend en charge un tas de formats de date différents mais pas celui-ci je suppose). Je souhaite modifier le format afin qu'il puisse être consommable par ma bibliothèque (en ISO par exemple). Comment puis-je faire ça ... J'ai trouvé un morceau de code qui pourrait aider mais ... où dois-je le mettre car je ne contrôle pas l'instanciation du sérialiseur Jackson (Jersey le fait)?

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

J'ai également trouvé ce code pour les JacksonJsonProvider personnalisés - la question est .. comment puis-je utiliser toutes mes classes POJO?

@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;
        }
    }
}
37
adrin

Pour ce qu'il vaut, ce nombre est standard Java (utilisé par les classes JDK); Unix stocke les secondes, Java millisecondes, c'est pourquoi sa valeur est légèrement supérieure) .

J'espère qu'il y a des documents sur la façon d'injecter ObjectMapper dans Jersey (cela devrait suivre la manière habituelle d'injecter l'objet fourni). Mais vous pouvez également remplacer JacksonJaxRsProvider pour spécifier/configurer ObjectMapper et l'enregistrer; c'est ce que fait Jersey lui-même, et il y a plusieurs façons de le faire.

6
StaxMan

J'ai réussi à le faire dans Resteasy "la manière JAX-RS", donc cela devrait fonctionner sur toutes les implémentations conformes comme Jersey (récemment testé avec succès sur le serveur JEE7 Wildfly 8, il a juste fallu quelques modifications à la partie Jackson parce qu'elles en ont changé quelques-unes) Apis).

Vous devez définir un ContextResolver (vérifiez que Produces contient le type de contenu correct):

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

}

Ensuite, vous devez renvoyer la classe nouvellement créée dans les getClasses de votre 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 cette façon, toutes les opérations effectuées via jackson reçoivent l'ObjectMapper de votre choix.

EDIT: J'ai récemment découvert à mes frais qu'en utilisant RestEasy 2.0.1 (et donc Jackson 1.5.3), il y a un comportement étrange si vous décidez d'étendre JacksonConfigurator pour ajouter des mappages personnalisés.

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

Si vous faites comme cela (et bien sûr, mettez la classe étendue dans RestApplication), le mappeur de la classe parent est utilisé, c'est-à-dire que vous perdez les mappages personnalisés. Pour le faire fonctionner correctement, j'ai dû faire quelque chose qui me semble inutile sinon:

public class MyJacksonConfigurator extends JacksonConfigurator implements ContextResolver<ObjectMapper> 
32
Riccardo Cossu

Pour configurer votre propre ObjectMapper, vous devez injecter votre propre classe qui implémente ContextResolver <ObjectMapper>

La façon exacte d'obtenir le maillot dépendra de votre IOC (printemps, guice). J'utilise le printemps, et ma classe ressemble à ceci:

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

Le code ci-dessous a fonctionné pour moi - 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);
  }
}
1
Prasobh.Kollattu

J'ai eu le même problème (en utilisant Jersey + Jackson + Json), le client envoyait une date, mais elle était modifiée sur le serveur lorsque les données ont été mappées dans l'objet.

J'ai suivi une autre approche pour résoudre ce problème, en lisant ce lien: http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html , quand j'ai réalisé que la date reçu était un TimeStamp (le même que Adrin dans sa question: "creationDate":1292236718456)

Dans ma classe VO, j'ai ajouté cette annotation à l'attribut @XmlJavaTypeAdapter et a également implémenté une classe interne qui a étendu 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;
      }
}

J'espère que cela pourrait aussi vous aider.

1
Ignacio Rubio

Si vous choisissez de travailler avec des objets Joda DateTime sur votre serveur et que vous souhaitez sérialiser en ISO8601, vous pouvez utiliser JodaModule de Jackson . Vous pouvez enregistrer un fournisseur de Jersey comme suit:

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

Plus d'informations disponibles sur site Internet de Jersey .

1
lorcan