Formato data Mappatura su JSON Jackson


154

Ho un formato data proveniente dall'API in questo modo:

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

Che è AAAA-GG-MM ORE: MM am / pm GMT. Sto mappando questo valore su una variabile Date in POJO. Ovviamente, mostra l'errore di conversione.

Vorrei sapere 2 cose:

  1. Qual è la formattazione che devo usare per effettuare la conversione con Jackson? Date è un buon tipo di campo per questo?
  2. In generale, c'è un modo per elaborare le variabili prima che vengano associate ai membri Object da Jackson? Qualcosa come cambiare il formato, i calcoli, ecc.

Questo è davvero un buon esempio, posiziona l'annotazione sul campo della classe: java.dzone.com/articles/how-serialize-javautildate
digz6666

Ora hanno una pagina wiki per la gestione della data: wiki.fasterxml.com/JacksonFAQDateHandling
Sutra

In questi giorni non dovresti più usare la Dateclasse. java.time, la moderna API di data e ora di Java, l' ha sostituita quasi 5 anni fa. Usalo e FasterXML / jackson-modules-java8 .
Ole VV,

Risposte:


124

Qual è la formattazione che devo usare per effettuare la conversione con Jackson? Date è un buon tipo di campo per questo?

Dateè un bel tipo di campo per questo. Puoi rendere JSON analizzabile abbastanza facilmente usando ObjectMapper.setDateFormat:

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

In generale, c'è un modo per elaborare le variabili prima che vengano associate ai membri Object da Jackson? Qualcosa come cambiare il formato, i calcoli, ecc.

Sì. Hai alcune opzioni, inclusa l'implementazione di un'abitudine JsonDeserializer, ad esempio l'estensione JsonDeserializer<Date>. Questo è un buon inizio.


2
Il formato 12 ore è migliore se il formato include anche la designazione AM / PM: DateFormat df = new SimpleDateFormat ("aaaa-MM-gg hh: mm a z");
John Scattergood,

Ho provato tutte queste soluzioni ma non sono stato in grado di memorizzare una variabile Date del mio POJO in un valore chiave della mappa, anche come data. Voglio che ciò crei un'istanza di BasicDbObject (API MongoDB) dalla mappa e di conseguenza memorizzi la variabile in una raccolta di database MongoDB come Data (non come Long o String). È possibile? Grazie
RedEagle il

1
È altrettanto semplice usare Java 8 LocalDateTimeo ZonedDateTimeinvece di Date? Poiché Dateè sostanzialmente deprecato (o almeno molti dei suoi metodi), vorrei usare quelle alternative.
houcros,

I javadocs per setSateFormat () affermano che questa chiamata rende ObjectMapper non più sicuro per Thread. Ho creato le classi JsonSerializer e JsonDeserializer.
MiguelMunoz,

Poiché la domanda non menziona java.util.Dateesplicitamente, desidero sottolineare che ciò non funziona per java.sql.Date.Vedere anche la mia risposta di seguito.
scimmia d'ottone,

329

Da Jackson v2.0, è possibile utilizzare l'annotazione @JsonFormat direttamente sui membri Object;

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

61
Se si desidera includere il fuso orario:@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
realPK

Ciao, quale vaso ha quell'annotazione. Sto usando la versione 1.9.10 di jackson-mapper-asl. Non lo capisco
Krishna Ram,

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

3
Questa annotazione funziona perfettamente solo in fase di serializzazione ma durante la deserializzazione le informazioni sul fuso orario e sulle impostazioni locali non vengono affatto utilizzate. Ho provato timezone = "CET" e fuso orario "Europa / Budapest" con locale = "hu" ma nessuno dei due funziona e causa strane conversazioni nel calendario. Solo la serializzazione personalizzata con deserializzazione ha funzionato per me gestendo il fuso orario come richiesto. Ecco il tutorial perfetto su come utilizzare baeldung.com/jackson-serialize-dates
Miklos Krivan,

1
Questo è un progetto jar separato chiamato Jackson Annotations . Esempio di immissione pom
bub

52

Naturalmente esiste un modo automatizzato chiamato serializzazione e deserializzazione e puoi definirlo con annotazioni specifiche ( @JsonSerialize , @JsonDeserialize ) come menzionato anche da pb2q.

Puoi usare sia java.util.Date che java.util.Calendar ... e probabilmente anche JodaTime.

Le annotazioni @JsonFormat non hanno funzionato per me come volevo (ha adattato il fuso orario a valori diversi) durante la deserializzazione (la serializzazione ha funzionato perfettamente):

@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")

È necessario utilizzare serializzatore personalizzato e deserializzatore personalizzato anziché l'annotazione @JsonFormat se si desidera il risultato previsto. Ho trovato un ottimo tutorial e una soluzione qui http://www.baeldung.com/jackson-serialize-dates

Ci sono esempi per i campi Data ma avevo bisogno dei campi Calendario , quindi ecco la mia implementazione :

La classe serializzatore :

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 di deserializzatore :

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

e l'uso delle classi di cui sopra:

public class CalendarEntry {

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

    // ... additional things ...
}

Utilizzando questa implementazione, l'esecuzione del processo di serializzazione e deserializzazione determina consecutivamente il valore dell'origine.

Solo usando l'annotazione @JsonFormat la deserializzazione dà risultati diversi, penso a causa della configurazione predefinita del fuso orario interno della biblioteca, cosa non si può cambiare con i parametri di annotazione (questa è stata la mia esperienza con la versione 2.5.3 e 2.6.3 della libreria Jackson).


4
Ho ricevuto un voto negativo per la mia risposta ieri. Ho lavorato molto su questo argomento, quindi non capisco. Posso avere un feedback per imparare da esso? Gradirei alcune note in caso di downvote. In questo modo possiamo imparare di più gli uni dagli altri.
Miklos Krivan,

Ottima risposta, grazie mi ha davvero aiutato! Piccolo suggerimento: prendere in considerazione l'inserimento di CustomCalendarSerializer e CustomCalendarDeserializer come classi statiche all'interno di una classe parent acclusa. Penso che questo renderebbe il codice un po 'più bello :)
Stuart,

@Stuart: dovresti semplicemente offrire questa riorganizzazione del codice come un'altra risposta o proporre una modifica. Miklos potrebbe non avere il tempo libero per farlo.
ocodo

@MiklosKrivan Ti ho sottovalutato per diversi motivi. Dovresti sapere che SimpleDateFormat non è sicuro per i thread e francamente dovresti usare librerie alternative per la formattazione della data (Joda, FastDateFormat di Common Commons, ecc.). L'altro motivo è l'impostazione del fuso orario e persino delle impostazioni internazionali. È molto più preferibile utilizzare GMT nell'ambiente di serializzazione e lasciare che il tuo client dica in quale fuso orario si trova o addirittura collegare la tz preferita come stringa separata. Imposta il tuo server su GMT aka UTC. Jackson ha incorporato il formato ISO.
Adam Gent,

1
Thx @AdamGent per il tuo feedback. Capisco e accetto i tuoi suggerimenti. Ma in questo caso specifico volevo solo concentrarmi sul fatto che l'annotazione JsonFormat con le informazioni sulla locale non funziona come ci aspetteremmo. E come si può risolvere.
Miklos Krivan,

4

Solo un esempio completo per l' applicazione di avvio a molla con RFC3339formato 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

Per aggiungere caratteri come T e Z nella tua data

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

produzione

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

3

Lavora per me. SpringBoot.

 import com.alibaba.fastjson.annotation.JSONField;

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

produzione:

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

2

Basandomi sulla risposta molto utile di @ miklov-kriven, spero che questi due ulteriori punti di considerazione possano rivelarsi utili a qualcuno:

(1) Trovo una buona idea includere serializzatore e deserializzatore come classi interne statiche nella stessa classe. NB, usando ThreadLocal per la sicurezza del thread di 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) In alternativa all'utilizzo delle annotazioni @JsonSerialize e @JsonDeserialize su ogni singolo membro della classe, si potrebbe anche considerare di sostituire la serializzazione predefinita di Jackson applicando la serializzazione personalizzata a livello di applicazione, ovvero tutti i membri della classe di tipo Data verranno serializzati da Jackson utilizzando questa serializzazione personalizzata senza annotazione esplicita su ciascun campo. Se si utilizza Spring Boot, ad esempio, un modo per farlo sarebbe il seguente:

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

Ti ho sottovalutato (odio il downvoting ma non voglio che le persone usino la tua risposta). SimpleDateFormat non è thread-safe. È il 2016 (hai risposto nel 2016). Non dovresti usare SimpleDateFormat quando ci sono numerose opzioni più veloci e thread-safe. C'è anche un abuso esatto di ciò che stai proponendo Q / A qui: stackoverflow.com/questions/25680728/…
Adam Gent,

2
@AdamGent grazie per averlo segnalato. In questo contesto, usando Jackson, la classe ObjectMapper è thread-safe quindi non importa. Tuttavia, ritengo che il codice possa essere copiato e utilizzato in un contesto sicuro senza thread. Quindi ho modificato la mia risposta per rendere sicuro l'accesso al thread SimpleDateFormat. Riconosco anche che ci sono alternative, principalmente il pacchetto java.time.
Stuart

2

Se qualcuno ha problemi con l'utilizzo di un formato data personalizzato per java.sql.Date, questa è la soluzione più semplice:

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

(Questa risposta SO mi ha risparmiato molti problemi: https://stackoverflow.com/a/35212795/3149048 )

Jackson utilizza SqlDateSerializer per impostazione predefinita per java.sql.Date, ma attualmente questo serializzatore non tiene conto del formato data, vedi questo problema: https://github.com/FasterXML/jackson-databind/issues/1407 . Per risolvere il problema, registrare un serializzatore diverso per java.sql.Date, come mostrato nell'esempio di codice.


1

Voglio sottolineare che l'impostazione di un SimpleDateFormatlike descritto nell'altra risposta funziona solo per un java.util.Dateche presumo sia inteso nella domanda. Ma per java.sql.Dateil formattatore non funziona. Nel mio caso non era molto ovvio il motivo per cui il formattatore non funzionava perché nel modello che doveva essere serializzato il campo era in realtà un java.utl.Datema l'oggetto reale finiva per essere un java.sql.Date. Questo è possibile perché

public class java.sql extends java.util.Date

Quindi questo è effettivamente valido

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

Quindi, se ti stai chiedendo perché il tuo campo Data non è formattato correttamente, assicurati che l'oggetto sia davvero un java.util.Date.

Qui viene anche menzionato il motivo per cui la gestione java.sql.Datenon verrà aggiunta.

Questo cambierebbe quindi il cambiamento, e non credo che sia giustificato. Se iniziassimo da zero sarei d'accordo con il cambiamento, ma poiché le cose non sono così tante.


1
Grazie per aver sottolineato una conseguenza di questo cattivo design delle due diverse Dateclassi. In questi giorni, tuttavia, non si dovrebbe nessuno di loro SimpleDateFormat. java.time, la moderna API di data e ora di Java, le ha sostituite quasi 5 anni fa. Usalo e FasterXML / jackson-modules-java8 .
Ole VV,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.