2012-09-17 15 views
82

Ho un formato di data provenienti da API in questo modo:Data Mapping a JSON Jackson

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

Quale è AAAA-MM-GG HH: MM am/pm GMT timestamp. Sto mappando questo valore a una variabile Date in POJO. Ovviamente, il suo errore di conversione mostra.

vorrei sapere 2 cose:

  1. Qual è la formattazione ho bisogno di usare per effettuare la conversione con Jackson? Data è un buon tipo di campo per questo?
  2. In generale, esiste un modo per elaborare le variabili prima che vengano mappate ai membri Object di Jackson? Qualcosa di simile, cambiando il formato, calcoli, ecc

risposta

70

Qual è la formattazione ho bisogno di usare per effettuare la conversione con Jackson? Data è un buon tipo di campo per questo?

Date è un tipo di campo bene per questo. È possibile effettuare il JSON parse-in grado 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 di essere mappati Object membri da Jackson? Qualcosa come, cambiando il formato, i calcoli, ecc.

Sì. Hai alcune opzioni, inclusa l'implementazione di una personalizzazione JsonDeserializer, ad es. che estende JsonDeserializer<Date>. This è 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"); –

+0

Provato tutte queste soluzioni ma non è stato in grado di memorizzare una variabile Data del mio POJO in un valore chiave Mappa, anche come Data. Desidero quindi creare un'istanza di BasicDbObject (MongoDB API) dalla mappa e di conseguenza memorizzare la variabile in una raccolta DB MongoDB come Date (non come Long o String). È possibile? Grazie – RedEagle

+1

È altrettanto facile usare Java 8 'LocalDateTime' o' ZonedDateTime' invece di 'Date'? Dato che 'Date' è fondamentalmente deprecato (o almeno molti dei suoi metodi), vorrei usare quelle alternative. – houcros

3

Questo è veramente buono esempio, posto l'annotazione sul campo della classe: http://java.dzone.com/articles/how-serialize-javautildate

+0

Questa soluzione non funziona se si sta tentando di impostare la configurazione globale per la serializzazione java.util.Date nell'applicazione, anche i modelli di dominio vengono contaminati. – realPK

+0

@PK non è una configurazione globale, devi aggiungere annotazioni al campo data dei tuoi modelli di dominio. – digz6666

1

Ora hanno una pagina wiki per la movimentazione Data: http://wiki.fasterxml.com/JacksonFAQDateHandling

+0

Fresco. Grazie, @Sutra. –

+0

Ciò è stato estremamente utile. È stato possibile impostarlo globalmente nell'app modificando il programma di mappatura degli oggetti con objMapper.configure (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); // scrivi le date come una stringa, ad es. 2015-07-06T20: 38: 41.960 + 0000 –

+1

Collegamento interrotto. A partire da questo momento, l'intero sito rifiuta le connessioni. – cbmeeks

205

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

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

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

+0

Ciao, quale jar ha quell'annotazione. Sto usando la versione 1.9.10 di jackson-mapper-asl. Io non lo capisco –

+1

@Ramki: annotazioni di jackson> = 2.0 –

37

Naturalmente c'è un modo automatizzato chiamato serializzazione e deserializzazione e si può definire con annotazioni specifiche (@JsonSerialize, @JsonDeserialize) Come indicato da pb2q pure.

È possibile utilizzare sia java.util.Date sia java.util.Calendar ... e probabilmente anche JodaTime.

Le annotazioni @JsonFormat non ha funzionato per me come volevoregolato il fuso orario di valore diverso) 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 per utilizzare il serializzatore personalizzato e il deserializzatore personalizzato invece dell'annotazione @JsonFormat se si desidera un risultato previsto. Ho trovato vero buon tutorial e la soluzione qui http://www.baeldung.com/jackson-serialize-dates

Ci sono esempi per Data campi, ma mi serviva per Calendario campi in modo qui è la mia realizzazione:

Il serializzatore classe:

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

Il deserializzatore c lass:

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'utilizzo delle classi 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 risultati consecutivamente il valore origine.

Solo utilizzando l'annotazione @JsonFormat la deserializzazione dà risultati diversi Credo che a causa della configurazione di default fuso orario interno biblioteca ciò che non si può cambiare con i parametri di annotazione (che era la mia esperienza con Jackson biblioteca 2.5.3 e 2.6. 3 versione pure).

+2

Ieri ho avuto un voto negativo per la mia risposta. Ho lavorato molto su questo argomento, quindi non capisco. Posso avere qualche feedback per imparare da esso? Apprezzerei alcune note in caso di downvote. In questo modo possiamo imparare di più gli uni dagli altri. –

+0

Ottima risposta, grazie mi ha davvero aiutato! Suggerimento minore: considera la possibilità di impostare CustomCalendarSerializer e CustomCalendarDeserializer come classi statiche all'interno di una classe parent chiusa.Penso che questo renderebbe il codice un po 'più bello :) – Stuart

+0

@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

1

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

(1) Trovo che sia una bella idea per includere serializzatore e de-serializzatore come statica interna classi della stessa classe. NB, utilizzando 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 utilizzando @JsonSerialize e @JsonDeserialize annotazioni su ogni singolo membro della classe si potrebbe anche prendere in considerazione l'override la serializzazione di default di Jackson applicando la serializzazione personalizzata a livello di applicazione, vale a dire tutti i membri della classe di type Date verrà serializzato da Jackson usando questa serializzazione personalizzata senza un'annotazione esplicita su ogni campo. Se si utilizza Primavera di avvio, ad esempio un modo per farlo sarebbe come segue:

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

Ti ho downgotato (odio il downvoting ma non voglio che la gente usi la tua risposta). SimpleDateFormat non è protetto da thread. È il 2016 (hai risposto nel 2016). Non dovresti utilizzare SimpleDateFormat quando ci sono numerose opzioni più veloci e sicure. Esiste anche un esatto uso improprio di ciò che stai proponendo Q/A qui: http://stackoverflow.com/questions/25680728/json-serializer-and-thread-safety –

+1

@AdamGent ringrazia per la segnalazione. 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 privo di thread. Così ho modificato la mia risposta per rendere sicuro l'accesso al thread di SimpleDateFormat. Riconosco inoltre che ci sono alternative, principalmente il pacchetto java.time. – Stuart

1

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

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

(Questo SO-risposta mi ha salvato un sacco di problemi: https://stackoverflow.com/a/35212795/3149048)

Jackson utilizza lo SqlDateSerializer per impostazione predefinita per java.sql.Date, ma attualmente, questo serializzatore non tiene conto del dataformat, vedere questo problema: https://github.com/FasterXML/jackson-databind/issues/1407. La soluzione alternativa consiste nel registrare un serializzatore diverso per java.sql.Date come mostrato nell'esempio di codice.