Datumsformat Zuordnung zu JSON Jackson

154

Ich habe ein Datumsformat von der API wie folgt:

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

Welches ist JJJJ-TT-MM HH: MM am / pm GMT Zeitstempel. Ich ordne diesen Wert einer Datumsvariablen in POJO zu. Offensichtlich zeigt es Konvertierungsfehler.

Ich möchte 2 Dinge wissen:

  1. Welche Formatierung muss ich verwenden, um die Konvertierung mit Jackson durchzuführen? Ist Datum ein guter Feldtyp dafür?
  2. Gibt es im Allgemeinen eine Möglichkeit, die Variablen zu verarbeiten, bevor sie von Jackson Objektmitgliedern zugeordnet werden? So etwas wie das Ändern des Formats, der Berechnungen usw.
Kevin Rave
quelle
Dies ist wirklich ein gutes Beispiel, platzieren Sie eine Anmerkung auf dem Feld der Klasse: java.dzone.com/articles/how-serialize-javautildate
digz6666
Jetzt haben sie eine Wiki-Seite für die Datumsbehandlung: wiki.fasterxml.com/JacksonFAQDateHandling
Sutra
In diesen Tagen sollten Sie die DateKlasse nicht mehr verwenden . java.time, die moderne Java-API für Datum und Uhrzeit, hat sie vor fast 5 Jahren ersetzt. Verwenden Sie es und FasterXML / jackson-modules-java8 .
Ole VV

Antworten:

124

Welche Formatierung muss ich verwenden, um die Konvertierung mit Jackson durchzuführen? Ist Datum ein guter Feldtyp dafür?

Dateist ein feiner Feldtyp dafür. Sie können die JSON-Analyse ziemlich einfach machen, indem Sie Folgendes verwenden ObjectMapper.setDateFormat:

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

Gibt es im Allgemeinen eine Möglichkeit, die Variablen zu verarbeiten, bevor sie von Jackson Objektmitgliedern zugeordnet werden? So etwas wie das Ändern des Formats, der Berechnungen usw.

Ja. Sie haben einige Optionen, einschließlich der Implementierung einer benutzerdefinierten Option JsonDeserializer, z JsonDeserializer<Date>. B. Erweiterung . Dies ist ein guter Anfang.

pb2q
quelle
2
Das 12-Stunden-Format ist besser, wenn das Format auch die AM / PM-Bezeichnung enthält: DateFormat df = new SimpleDateFormat ("JJJJ-MM-TT hh: mm a z");
John Scattergood
Versuchte alle diese Lösungen, konnte jedoch keine Datumsvariable meines POJO in einem Map-Schlüsselwert speichern, auch nicht als Datum. Ich möchte, dass dies dann ein BasicDbObject (MongoDB-API) aus der Map instanziiert und folglich die Variable in einer MongoDB-DB-Auflistung als Datum (nicht als Long oder String) speichert. Ist das überhaupt möglich? Vielen Dank
RedEagle
1
Ist es genauso einfach, Java 8 zu verwenden LocalDateTimeoder ZonedDateTimestatt Date? Da Datees grundsätzlich veraltet ist (oder zumindest viele seiner Methoden), möchte ich diese Alternativen verwenden.
Houcros
Die Javadocs für setSateFormat () besagen, dass dieser Aufruf den ObjectMapper nicht mehr Thread-sicher macht. Ich habe die Klassen JsonSerializer und JsonDeserializer erstellt.
Miguel Munoz
Da die Frage nicht java.util.Dateausdrücklich erwähnt wird, möchte ich darauf hinweisen, dass dies nicht funktioniert. java.sql.Date.Siehe auch meine Antwort unten.
Messingaffe
329

Seit Jackson v2.0 können Sie die Annotation @JsonFormat direkt für Objektmitglieder verwenden.

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;
Olivier Lecrivain
quelle
61
Wenn Sie Zeitzone @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
einschließen
Hallo, welches Glas hat diese Anmerkung? Ich verwende die Version Jackson-Mapper-ASL ​​1.9.10. Ich verstehe es nicht
krishna Ram
1
@ Ramki: Jackson-Anmerkungen> = 2.0
Olivier Lecrivain
3
Diese Annotation funktioniert nur in der Serialisierungsphase einwandfrei, während der Deserialisierung werden jedoch die Zeitzonen- und Gebietsschemainformationen überhaupt nicht verwendet. Ich habe timezone = "CET" und timezone "Europe / Budapest" mit locale = "hu" ausprobiert, aber keines funktioniert und verursacht seltsame Zeitgespräche im Kalender. Nur die benutzerdefinierte Serialisierung mit Deserialisierung funktionierte für mich, um die Zeitzone nach Bedarf zu behandeln. Hier ist das perfekte Tutorial, wie Sie baeldung.com/jackson-serialize-dates
Miklos Krivan
1
Dies ist ein separates Jar-Projekt namens Jackson Annotations . Beispiel Pom Eintrag
Bub
52

Natürlich gibt es eine automatisierte Methode namens Serialisierung und Deserialisierung, die Sie mit bestimmten Anmerkungen ( @JsonSerialize , @JsonDeserialize ) definieren können, wie auch von pb2q erwähnt.

Sie können sowohl java.util.Date als auch java.util.Calendar ... und wahrscheinlich auch JodaTime verwenden.

Die @ JsonFormat-Annotationen funktionierten bei mir nicht wie gewünscht ( die Zeitzone wurde auf einen anderen Wert eingestellt) während der Deserialisierung (die Serialisierung funktionierte perfekt):

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

Sie müssen einen benutzerdefinierten Serializer und einen benutzerdefinierten Deserializer anstelle der Annotation @JsonFormat verwenden, wenn Sie ein vorhergesagtes Ergebnis wünschen. Ich habe hier ein wirklich gutes Tutorial und eine Lösung gefunden http://www.baeldung.com/jackson-serialize-dates

Es gibt Beispiele für Datum Felder aber ich brauchte für Kalender Felder so hier ist meine Umsetzung :

Die Serializer- Klasse:

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

Die Deserializer- Klasse:

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

und die Verwendung der oben genannten Klassen:

public class CalendarEntry {

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

    // ... additional things ...
}

Unter Verwendung dieser Implementierung ergibt die Ausführung des Serialisierungs- und Deserialisierungsprozesses nacheinander den Ursprungswert.

Nur mit der Annotation @JsonFormat liefert die Deserialisierung ein anderes Ergebnis. Ich denke, aufgrund der Standardeinstellung für die interne Zeitzone der Bibliothek können Sie diese mit den Annotationsparametern nicht ändern (das war auch meine Erfahrung mit den Versionen Jackson Library 2.5.3 und 2.6.3).

Miklos Krivan
quelle
4
Ich habe gestern meine Antwort abgelehnt. Ich habe viel an diesem Thema gearbeitet, deshalb verstehe ich es nicht. Darf ich ein Feedback bekommen, um daraus zu lernen? Ich würde mich über einige Anmerkungen im Falle einer Ablehnung freuen. Auf diese Weise können wir mehr voneinander lernen.
Miklos Krivan
Tolle Antwort, danke, es hat mir wirklich geholfen! Kleiner Vorschlag - Ziehen Sie in Betracht, CustomCalendarSerializer und CustomCalendarDeserializer als statische Klassen in eine umschließende übergeordnete Klasse einzufügen. Ich denke, das würde den Code ein wenig schöner machen :)
Stuart
@Stuart - Sie sollten diese Neuorganisation des Codes einfach als weitere Antwort anbieten oder eine Bearbeitung vorschlagen. Miklos hat möglicherweise nicht die Zeit, dies zu tun.
ocodo
@MiklosKrivan Ich habe dich aus mehreren Gründen abgelehnt. Sie sollten wissen, dass SimpleDateFormat nicht threadsicher ist, und ehrlich gesagt sollten Sie alternative Bibliotheken für die Datumsformatierung verwenden (Joda, Commons-lang FastDateFormat usw.). Der andere Grund ist die Einstellung der Zeitzone und sogar des Gebietsschemas. Es ist weitaus vorzuziehen, GMT in einer Serialisierungsumgebung zu verwenden und Ihren Client sagen zu lassen, in welcher Zeitzone es sich befindet, oder das bevorzugte tz sogar als separate Zeichenfolge anzuhängen. Stellen Sie Ihren Server auf GMT aka UTC ein. Jackson hat das ISO-Format eingebaut.
Adam Gent
1
Danke @AdamGent für dein Feedback. Ich verstehe und akzeptiere Ihre Vorschläge. In diesem speziellen Fall wollte ich mich jedoch nur darauf konzentrieren, dass die JsonFormat-Annotation mit Gebietsschemainformationen nicht wie erwartet funktioniert. Und wie kann gelöst werden.
Miklos Krivan
4

Nur ein vollständiges Beispiel für eine Spring-Boot- Anwendung im RFC3339Datetime-Format

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
quelle
4

Hinzufügen von Zeichen wie T und Z zu Ihrem Datum

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

Ausgabe

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

Ich arbeite für mich. SpringBoot.

 import com.alibaba.fastjson.annotation.JSONField;

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

Ausgabe:

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

Aufbauend auf der sehr hilfreichen Antwort von @ miklov-kriven hoffe ich, dass sich diese beiden zusätzlichen Überlegungen für jemanden als hilfreich erweisen:

(1) Ich finde es eine gute Idee, Serializer und De-Serializer als statische innere Klassen in dieselbe Klasse aufzunehmen. Hinweis: Verwenden von ThreadLocal für die Thread-Sicherheit von 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) Alternativ zur Verwendung von Annotationen @JsonSerialize und @JsonDeserialize für jedes einzelne Klassenmitglied können Sie auch die Standard-Serialisierung von Jackson überschreiben, indem Sie die benutzerdefinierte Serialisierung auf Anwendungsebene anwenden. Dies bedeutet, dass alle Klassenmitglieder vom Typ Datum von Jackson serialisiert werden Verwenden dieser benutzerdefinierten Serialisierung ohne explizite Anmerkung für jedes Feld. Wenn Sie beispielsweise Spring Boot verwenden, haben Sie folgende Möglichkeiten:

@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
quelle
Ich habe dich herabgestimmt (ich hasse Downvoting, aber ich möchte einfach nicht, dass die Leute deine Antwort verwenden). SimpleDateFormat ist nicht threadsicher. Es ist 2016 (Sie haben 2016 geantwortet). Sie sollten SimpleDateFormat nicht verwenden, wenn es zahlreiche schnellere und threadsichere Optionen gibt. Es gibt sogar einen genauen Missbrauch dessen, was Sie Q / A hier vorschlagen
Adam Gent
2
@AdamGent danke für den Hinweis. In diesem Zusammenhang ist die ObjectMapper-Klasse unter Verwendung von Jackson threadsicher, sodass dies keine Rolle spielt. Ich gehe jedoch davon aus, dass der Code möglicherweise kopiert und in einem nicht threadsicheren Kontext verwendet wird. Daher habe ich meine Antwort bearbeitet, um den Zugriff auf den SimpleDateFormat-Thread sicher zu machen. Ich erkenne auch an, dass es Alternativen gibt, vor allem das Paket java.time.
Stuart
2

Wenn jemand Probleme mit der Verwendung eines benutzerdefinierten Datumsformats für java.sql.Date hat, ist dies die einfachste Lösung:

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

(Diese SO-Antwort hat mir viel Ärger erspart: https://stackoverflow.com/a/35212795/3149048 )

Jackson verwendet den SqlDateSerializer standardmäßig für java.sql.Date. Derzeit berücksichtigt dieser Serializer jedoch nicht das Datumsformat. Weitere Informationen finden Sie in diesem Problem: https://github.com/FasterXML/jackson-databind/issues/1407 . Die Problemumgehung besteht darin, einen anderen Serializer für java.sql.Date zu registrieren, wie im Codebeispiel gezeigt.

Stephanie
quelle
1

Ich möchte darauf hinweisen, dass das Setzen eines SimpleDateFormatähnlichen wie in der anderen Antwort beschriebenen nur für ein funktioniert, von java.util.Datedem ich annehme, dass es in der Frage gemeint ist. Aber für java.sql.Dateden Formatierer funktioniert das nicht. In meinem Fall war es nicht sehr offensichtlich, warum der Formatierer nicht funktionierte, da in dem Modell, das serialisiert werden sollte, das Feld tatsächlich a war java.utl.Date, das eigentliche Objekt jedoch a java.sql.Date. Dies ist möglich, weil

public class java.sql extends java.util.Date

Das ist also tatsächlich gültig

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

Wenn Sie sich also fragen, warum Ihr Datumsfeld nicht richtig formatiert ist, stellen Sie sicher, dass das Objekt wirklich ein Objekt ist java.util.Date .

Hier wird auch erwähnt, warum die Handhabung java.sql.Datenicht hinzugefügt wird.

Dies würde dann die Veränderung brechen, und ich denke nicht, dass dies gerechtfertigt ist. Wenn wir von vorne anfangen würden, würde ich der Änderung zustimmen, aber da die Dinge nicht so sehr sind.

Messingaffe
quelle
1
Vielen Dank, dass Sie auf eine Konsequenz dieses schlechten Designs der beiden verschiedenen DateKlassen hingewiesen haben . Heutzutage sollte man aber auch keinen von ihnen haben SimpleDateFormat. java.time, die moderne Java-API für Datum und Uhrzeit, hat sie vor fast 5 Jahren ersetzt. Verwenden Sie es und FasterXML / jackson-modules-java8 .
Ole VV