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.
Kevin Rave
la source
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.

pb2q
la source
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;
Olivier Lecrivain
la source
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).

Miklos Krivan
la source
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 [email protected] 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"));
    }
}
BaiJiFeiLong
la source
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"
}
Odwori
la source
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"
}
Anson
la source
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;
    }
}
Stuart
la source
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.

Stéphanie
la source
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.

singe en laiton
la source
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