Mappage du format de date vers JSON Jackson


154

J'ai un format de date provenant de l'API comme celui-ci:

"start_time": "2015-10-1 3:00 PM GMT+1:00"

Qui est AAAA-JJ-MM HH: MM am / pm GMT horodatage. Je mappe cette valeur à une variable de date dans POJO. De toute évidence, il affiche une erreur de conversion.

J'aimerais savoir 2 choses:

  1. Quel format dois-je utiliser pour effectuer une conversion avec Jackson? La date est-elle un bon type de champ pour cela?
  2. En général, existe-t-il un moyen de traiter les variables avant qu'elles ne soient mappées aux membres Object par Jackson? Quelque chose comme, changer le format, les calculs, etc.

C'est vraiment un bon exemple, placez une annotation sur le champ de la classe: java.dzone.com/articles/how-serialize-javautildate
digz6666

Maintenant, ils ont une page wiki pour la gestion des dates: wiki.fasterxml.com/JacksonFAQDateHandling
Sutra

Ces jours-ci, vous ne devriez plus utiliser la Dateclasse. java.time, l'API Java de date et d'heure moderne, l' a remplacé il y a près de 5 ans. Utilisez-le et FasterXML / jackson-modules-java8 .
Ole VV

Réponses:


125

Quel format dois-je utiliser pour effectuer une conversion avec Jackson? La date est-elle un bon type de champ pour cela?

Dateest un type de champ fin pour cela. Vous pouvez rendre l'analyse JSON assez facilement en utilisant ObjectMapper.setDateFormat:

DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
myObjectMapper.setDateFormat(df);

En général, existe-t-il un moyen de traiter les variables avant qu'elles ne soient mappées aux membres Object par Jackson? Quelque chose comme, changer le format, les calculs, etc.

Oui. Vous avez quelques options, y compris l'implémentation d'un custom JsonDeserializer, par exemple l'extension JsonDeserializer<Date>. C'est un bon début.


2
Le format 12 heures est meilleur si le format inclut également la désignation AM / PM: DateFormat df = new SimpleDateFormat ("aaaa-MM-jj hh: mm a z");
John Scattergood

J'ai essayé toutes ces solutions, mais je n'ai pas été en mesure de stocker une variable de date de mon POJO dans une valeur de clé de carte, également en tant que date. Je veux que cela instancie ensuite un BasicDbObject (API MongoDB) à partir de la carte et, par conséquent, stocke la variable dans une collection MongoDB DB en tant que Date (pas en tant que Long ou String). Est-ce seulement possible? Merci
RedEagle

1
Est-il aussi simple d'utiliser Java 8 LocalDateTimeou à la ZonedDateTimeplace de Date? Étant donné que Datec'est fondamentalement obsolète (ou du moins beaucoup de ses méthodes), j'aimerais utiliser ces alternatives.
houcros

Les javadocs pour setSateFormat () indiquent que cet appel rend l'ObjectMapper plus sûr pour les threads. J'ai créé les classes JsonSerializer et JsonDeserializer.
MiguelMunoz

Comme la question ne le mentionne pas java.util.Dateexplicitement, je tiens à souligner que cela ne fonctionne pas pour java.sql.Date.Voir également ma réponse ci-dessous.
singe en laiton

329

Depuis Jackson v2.0, vous pouvez utiliser l'annotation @JsonFormat directement sur les membres Object;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;

61
Si vous souhaitez inclure le fuseau horaire:@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
realPK

Salut, quel pot a cette annotation. J'utilise la version jackson-mapper-asl 1.9.10. Je ne comprends pas
Krishna Ram

1
@Ramki: jackson-annotations> = 2.0
Olivier Lecrivain

3
Cette annotation ne fonctionne parfaitement qu'en phase de sérialisation, mais pendant la désérialisation, le fuseau horaire et les informations locales ne sont pas du tout utilisés. J'ai essayé timezone = "CET" et fuseau horaire "Europe / Budapest" avec locale = "hu" mais aucun des deux ne fonctionne et ne provoque d'étranges conversations sur le calendrier. Seule la sérialisation personnalisée avec désérialisation a fonctionné pour moi en gérant le fuseau horaire comme requis. Voici le tutoriel parfait sur la façon dont vous devez utiliser baeldung.com/jackson-serialize-dates
Miklos Krivan

1
Il s'agit d'un projet jar distinct appelé Jackson Annotations . Exemple d' entrée de pom
bub

52

Bien sûr, il existe une méthode automatisée appelée sérialisation et désérialisation et vous pouvez la définir avec des annotations spécifiques ( @JsonSerialize , @JsonDeserialize ) comme mentionné par pb2q.

Vous pouvez utiliser à la fois java.util.Date et java.util.Calendar ... et probablement JodaTime également.

Les annotations @JsonFormat n'ont pas fonctionné pour moi comme je le voulais (il a ajusté le fuseau horaire à une valeur différente) lors de la désérialisation (la sérialisation a parfaitement fonctionné):

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "CET")

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Europe/Budapest")

Vous devez utiliser un sérialiseur personnalisé et un désérialiseur personnalisé au lieu de l'annotation @JsonFormat si vous voulez un résultat prédit. J'ai trouvé un très bon tutoriel et une solution ici http://www.baeldung.com/jackson-serialize-dates

Il existe des exemples pour les champs Date mais j'avais besoin pour les champs Calendrier , voici donc mon implémentation :

La classe de sérialiseur :

public class CustomCalendarSerializer extends JsonSerializer<Calendar> {

    public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    public static final Locale LOCALE_HUNGARIAN = new Locale("hu", "HU");
    public static final TimeZone LOCAL_TIME_ZONE = TimeZone.getTimeZone("Europe/Budapest");

    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider arg2)
            throws IOException, JsonProcessingException {
        if (value == null) {
            gen.writeNull();
        } else {
            gen.writeString(FORMATTER.format(value.getTime()));
        }
    }
}

La classe deserializer :

public class CustomCalendarDeserializer extends JsonDeserializer<Calendar> {

    @Override
    public Calendar deserialize(JsonParser jsonparser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        String dateAsString = jsonparser.getText();
        try {
            Date date = CustomCalendarSerializer.FORMATTER.parse(dateAsString);
            Calendar calendar = Calendar.getInstance(
                CustomCalendarSerializer.LOCAL_TIME_ZONE, 
                CustomCalendarSerializer.LOCALE_HUNGARIAN
            );
            calendar.setTime(date);
            return calendar;
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

et l'utilisation des classes ci-dessus:

public class CalendarEntry {

    @JsonSerialize(using = CustomCalendarSerializer.class)
    @JsonDeserialize(using = CustomCalendarDeserializer.class)
    private Calendar calendar;

    // ... additional things ...
}

En utilisant cette implémentation, l'exécution du processus de sérialisation et de désérialisation donne consécutivement la valeur d'origine.

En utilisant uniquement l'annotation @JsonFormat, la désérialisation donne un résultat différent Je pense qu'en raison de la configuration par défaut du fuseau horaire interne de la bibliothèque, ce que vous ne pouvez pas changer avec les paramètres d'annotation (c'était également mon expérience avec les versions 2.5.3 et 2.6.3 de la bibliothèque Jackson).


4
J'ai voté contre ma réponse hier. J'ai beaucoup travaillé sur ce sujet donc je ne comprends pas. Puis-je avoir des commentaires à en tirer? J'apprécierais quelques notes en cas de vote défavorable. De cette façon, nous pouvons apprendre davantage les uns des autres.
Miklos Krivan

Excellente réponse, merci, cela m'a vraiment aidé! Suggestion mineure - envisagez de placer CustomCalendarSerializer et CustomCalendarDeserializer en tant que classes statiques dans une classe parent englobante. Je pense que cela rendrait le code un peu plus agréable :)
Stuart

@Stuart - vous devez simplement proposer cette réorganisation du code comme une autre réponse, ou proposer une modification. Miklos n'a peut-être pas le temps de le faire.
ocodo

@MiklosKrivan Je vous ai décliné pour plusieurs raisons. Vous devez savoir que SimpleDateFormat n'est pas threadsafe et franchement, vous devez utiliser des bibliothèques alternatives pour le formatage de la date (Joda, Commons-lang FastDateFormat, etc.). L'autre raison est le réglage du fuseau horaire et même des paramètres régionaux. Il est de loin préférable d'utiliser GMT dans un environnement de sérialisation et de laisser votre client dire dans quel fuseau horaire il se trouve ou même de joindre le tz préféré sous forme de chaîne distincte. Réglez votre serveur sur GMT aka UTC. Jackson a intégré le format ISO.
Adam Gent

1
Merci @AdamGent pour vos commentaires. Je comprends et j'accepte vos suggestions. Mais dans ce cas particulier, je voulais juste souligner que l'annotation JsonFormat avec les informations locales ne fonctionne pas comme prévu. Et comment peut être résolu.
Miklos Krivan

4

Juste un exemple complet d' application Spring Boot avec le RFC3339format datetime

package bj.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

import java.text.SimpleDateFormat;

/**
 * Created by BaiJiFeiLong@gmail.com at 2018/5/4 10:22
 */
@SpringBootApplication
public class BarApp implements ApplicationListener<ApplicationReadyEvent> {

    public static void main(String[] args) {
        SpringApplication.run(BarApp.class, args);
    }

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
    }
}

4

Pour ajouter des caractères tels que T et Z dans votre date

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
private Date currentTime;

production

{
    "currentTime": "2019-12-11T11:40:49Z"
}

3

Travailler pour moi. SpringBoot.

 import com.alibaba.fastjson.annotation.JSONField;

 @JSONField(format = "yyyy-MM-dd HH:mm:ss")  
 private Date createTime;

production:

{ 
   "createTime": "2019-06-14 13:07:21"
}

2

En m'appuyant sur la réponse très utile de @ miklov-kriven, j'espère que ces deux points de considération supplémentaires seront utiles à quelqu'un:

(1) Je trouve que c'est une bonne idée d'inclure le sérialiseur et le désérialiseur en tant que classes internes statiques dans la même classe. NB, en utilisant ThreadLocal pour la sécurité des threads de SimpleDateFormat.

public class DateConverter {

    private static final ThreadLocal<SimpleDateFormat> sdf = 
        ThreadLocal.<SimpleDateFormat>withInitial(
                () -> {return new SimpleDateFormat("yyyy-MM-dd HH:mm a z");});

    public static class Serialize extends JsonSerializer<Date> {
        @Override
        public void serialize(Date value, JsonGenerator jgen SerializerProvider provider) throws Exception {
            if (value == null) {
                jgen.writeNull();
            }
            else {
                jgen.writeString(sdf.get().format(value));
            }
        }
    }

    public static class Deserialize extends JsonDeserializer<Date> {
        @Overrride
        public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws Exception {
            String dateAsString = jp.getText();
            try {
                if (Strings.isNullOrEmpty(dateAsString)) {
                    return null;
                }
                else {
                    return new Date(sdf.get().parse(dateAsString).getTime());
                }
            }
            catch (ParseException pe) {
                throw new RuntimeException(pe);
            }
        }
    }
}

(2) Au lieu d'utiliser les annotations @JsonSerialize et @JsonDeserialize sur chaque membre de classe individuel, vous pouvez également envisager de remplacer la sérialisation par défaut de Jackson en appliquant la sérialisation personnalisée au niveau de l'application, c'est-à-dire que tous les membres de classe de type Date seront sérialisés par Jackson en utilisant cette sérialisation personnalisée sans annotation explicite sur chaque champ. Si vous utilisez Spring Boot par exemple, une façon de le faire serait la suivante:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Module customModule() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(Date.class, new DateConverter.Serialize());
        module.addDeserializer(Date.class, new Dateconverter.Deserialize());
        return module;
    }
}

Je vous ai refusé (je déteste le vote négatif, mais je ne veux tout simplement pas que les gens utilisent votre réponse). SimpleDateFormat n'est pas threadsafe. Nous sommes en 2016 (vous avez répondu en 2016). Vous ne devriez pas utiliser SimpleDateFormat lorsqu'il existe de nombreuses options plus rapides et threadsafe. Il y a même une mauvaise utilisation exacte de ce que vous proposez Q / R ici: stackoverflow.com/questions/25680728/…
Adam Gent

2
@AdamGent merci pour l'avoir signalé. Dans ce contexte, en utilisant Jackson, la classe ObjectMapper est thread-safe donc cela n'a pas d'importance. Cependant, je comprends votre point de vue selon lequel le code peut être copié et utilisé dans un contexte non thread-safe. J'ai donc modifié ma réponse pour rendre l'accès à SimpleDateFormat en toute sécurité. Je reconnais également qu'il existe des alternatives, principalement le package java.time.
Stuart

2

Si quelqu'un a des problèmes avec l'utilisation d'un format de date personnalisé pour java.sql.Date, voici la solution la plus simple:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(java.sql.Date.class, new DateSerializer());
mapper.registerModule(module);

(Cette réponse SO m'a évité beaucoup de problèmes: https://stackoverflow.com/a/35212795/3149048 )

Jackson utilise le SqlDateSerializer par défaut pour java.sql.Date, mais actuellement, ce sérialiseur ne prend pas en compte le format de date, voir ce problème: https://github.com/FasterXML/jackson-databind/issues/1407 . La solution de contournement consiste à enregistrer un sérialiseur différent pour java.sql.Date, comme indiqué dans l'exemple de code.


1

Je tiens à souligner que la définition d'un SimpleDateFormatcomme décrit dans l'autre réponse ne fonctionne que pour un java.util.Datequi, je suppose, est destiné à la question. Mais pour java.sql.Datele formateur ne fonctionne pas. Dans mon cas, il n'était pas très évident de savoir pourquoi le formateur ne fonctionnait pas parce que dans le modèle qui devait être sérialisé, le champ était en fait un java.utl.Datemais l'objet réel a fini par être un java.sql.Date. Ceci est possible car

public class java.sql extends java.util.Date

Donc c'est en fait valable

java.util.Date date = new java.sql.Date(1542381115815L);

Donc, si vous vous demandez pourquoi votre champ Date n'est pas correctement formaté, assurez-vous que l'objet est vraiment un java.util.Date.

Voici également pourquoi la manipulation java.sql.Datene sera pas ajoutée.

Ce serait alors un changement radical, et je ne pense pas que ce soit justifié. Si nous partions de zéro, je serais d'accord avec le changement, mais comme les choses ne sont pas tellement.


1
Merci d'avoir signalé une conséquence de cette mauvaise conception des deux Dateclasses différentes . De nos jours, on ne devrait pas non plus aucun d'entre eux SimpleDateFormat. java.time, l'API Java de date et d'heure moderne, les a remplacés il y a près de 5 ans. Utilisez-le et FasterXML / jackson-modules-java8 .
Ole VV
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.