Java 8 LocalDate Jackson-Format


138

Für java.util.Date, wenn ich es tue

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")  
  private Date dateOfBirth;

dann in JSON-Anfrage, wenn ich sende

{ {"dateOfBirth":"01/01/2000"} }  

Es klappt.

Wie soll ich das für das LocalDate- Feld von Java 8 machen ?

Ich habe es versucht

@JsonDeserialize(using = LocalDateDeserializer.class)  
@JsonSerialize(using = LocalDateSerializer.class)  
private LocalDate dateOfBirth;  

Es hat nicht funktioniert.

Kann mir bitte jemand mitteilen, wie das richtig ist?

Unten finden Sie Abhängigkeiten

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>jaxrs-api</artifactId>
     <version>3.0.9.Final</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.4.2</version>
</dependency>
<dependency>
    <groupId>com.wordnik</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>1.3.10</version>
</dependency>

Antworten:


106

Ich war nie in der Lage, dies mithilfe von Anmerkungen einfach zum Laufen zu bringen. Damit es funktioniert, habe ich ein ContextResolverfor erstellt ObjectMapperund dann das JSR310Module( Update: jetzt ist es JavaTimeModulestattdessen ) hinzugefügt , zusammen mit einer weiteren Einschränkung, bei der das Schreibdatum als Zeitstempel auf false gesetzt werden musste. Weitere Informationen finden Sie in der Dokumentation zum JSR310-Modul . Hier ist ein Beispiel für das, was ich verwendet habe.

Abhängigkeit

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.4.0</version>
</dependency>

Hinweis: Ein Problem, mit dem ich konfrontiert war, ist, dass die jackson-annotationVersion, die von einer anderen Abhängigkeit abgerufen wurde, Version 2.3.2 verwendete, die die von der jsr310. Was passiert ist, war, dass ich ein NoClassDefFound für ObjectIdResolvereine 2.4-Klasse bekommen habe. Also musste ich nur die enthaltenen Abhängigkeitsversionen ausrichten

ContextResolver

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {  
    private final ObjectMapper MAPPER;

    public ObjectMapperContextResolver() {
        MAPPER = new ObjectMapper();
        // Now you should use JavaTimeModule instead
        MAPPER.registerModule(new JSR310Module());
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return MAPPER;
    }  
}

Ressourcenklasse

@Path("person")
public class LocalDateResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getPerson() {
        Person person = new Person();
        person.birthDate = LocalDate.now();
        return Response.ok(person).build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createPerson(Person person) {
        return Response.ok(
                DateTimeFormatter.ISO_DATE.format(person.birthDate)).build();
    }

    public static class Person {
        public LocalDate birthDate;
    }
}

Prüfung

curl -v http://localhost:8080/api/person
Ergebnis: {"birthDate":"2015-03-01"}

curl -v -POST -H "Content-Type:application/json" -d "{\"birthDate\":\"2015-03-01\"}" http://localhost:8080/api/person
Ergebnis: 2015-03-01


Siehe auch hier für die JAXB-Lösung.

AKTUALISIEREN

Das JSR310Moduleist ab Version 2.7 von Jackson veraltet. Stattdessen sollten Sie das Modul registrieren JavaTimeModule. Es ist immer noch die gleiche Abhängigkeit.


1
Hallo Peeskillet, das Feld Geburtsdatum wird als "Geburtsdatum" generiert: {"Jahr": 0, "Monat": "Monat", "Tag des Monats": 0, "Tag der Woche": "Tag der Woche", "Ära": {" value ": 0}," dayOfYear ": 0," leapYear ": false," monthValue ": 0," chronology ": {" id ":" "," calendarType ":"}} Wie kann ich es einfach machen? als "Geburtsdatum" ???
JAB

Überprüfen Sie, ob der ContextResolver aufgerufen wird. Fügen Sie der getContextMethode eine print-Anweisung hinzu . Wenn diese Methode aufgerufen wird, sehe ich keinen Grund dafür, dass dies nicht funktioniert. Wenn es nicht aufgerufen wird, muss es möglicherweise mit der App-Konfiguration behoben werden. Dafür müsste ich mehr sehen, als Sie zur Verfügung gestellt haben. Wie Resteasy-Version, Abhängigkeiten, App-Konfiguration entweder web.xml oder Application-Unterklasse. Grundsätzlich genug, um das Problem zu reproduzieren
Paul Samsotha

ContextResolver heißt nicht Peeskillet. Ich registriere es in web.xml als <Kontextparameter> <Parametername> resteasy.resources </ param-name> <param-value> com.bac.ObjectMapperContextResolver </ param-value> </ context-param> aktualisierte Frage für Abhängigkeiten, die ich benutze
JAB

Swagger scheint das Problem zu sein. Ich würde sagen, es zu deaktivieren, aber angesichts dieser Frage wurde ein Problem eingereicht, mit einem Konflikt zwischen Swagger's ObjectMapper und dem Versuch, Ihren eigenen zu verwenden. Sie können versuchen, ihre zu deaktivieren, und im ContextResolver alle Konfigurationen auf " ObjectMapperswagger" einstellen (in der Frage sehen Sie einen Link). Ich weiß es nicht, da ich nicht viel mit Prahlerei arbeite. Aber ich denke, Prahlerei ist das Hauptproblem, warum der Contextresolver nicht aufgerufen wird.
Paul Samsotha

Nach weiteren Tests funktioniert die Anmerkung . Auch wenn wir haben Swagger ObjectMapper verwenden, dann konfigurieren bereits die Zeitstempel als falsch für uns. Das sollte also funktionieren. Für eine bessere Hilfe empfehle ich dringend, dass Sie eine MCVE bereitstellen , die das Problem demonstriert.
Paul Samsotha

95

@JsonSerialize und @JsonDeserialize haben bei mir gut funktioniert. Sie machen das zusätzliche jsr310-Modul überflüssig:

@JsonDeserialize(using = LocalDateDeserializer.class)  
@JsonSerialize(using = LocalDateSerializer.class)  
private LocalDate dateOfBirth;

Deserializer:

public class LocalDateDeserializer extends StdDeserializer<LocalDate> {

    private static final long serialVersionUID = 1L;

    protected LocalDateDeserializer() {
        super(LocalDate.class);
    }


    @Override
    public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        return LocalDate.parse(jp.readValueAs(String.class));
    }

}

Serializer:

public class LocalDateSerializer extends StdSerializer<LocalDate> {

    private static final long serialVersionUID = 1L;

    public LocalDateSerializer(){
        super(LocalDate.class);
    }

    @Override
    public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider sp) throws IOException, JsonProcessingException {
        gen.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
    }
}

2
Dies ist die beste Antwort für mich. Vielen Dank!
Romeo Jr. Maranan

7
Diese Klassen sind in enthalten jackson-datatype-jsr310. Sie müssen sie nicht manuell in Ihrem Projekt definieren.
NeuroXc

1
Diese Lösung funktionierte bei mir mit den Serialisierern in jackson-datatype-jsr310.
Dave

2
Dies sollte die neue beste Antwort sein.
Doktor Parameter

Wenn Sie Serializer und Deserializer in jackson-datatype-jsr310 verwenden, fügen Sie Ihrem Feld besser @JsonFormat (shape = JsonFormat.Shape.STRING) hinzu. Ohne dieses Format wird der Wert als [Jahr, Monat, Tag] serialisiert, obwohl die Deserialisierung funktioniert.
Jian Chen

74
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

funktioniert gut für mich.


2
new com.fasterxml.jackson.datatype.jsr310.JSR310Module()für Version 2.5.4 von Jackson. Die JavaTimeModule-Klasse ist in dieser Version nicht vorhanden.
user3774109

Diese Antwort funktioniert auch für LocalDateTime(Jackson 2.9.5). 1 zusätzliche Abhängigkeit erforderlich, so sieht meine build.sbt wie folgt aus:"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.5", "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.5"
ruhong

2
Dies sollte viel mehr Stimmen haben. Einfach und effektiv.
Mkasberg

Hat perfekt funktioniert! Danke dir.
MAD-HAX

Dies zeigte mir die richtige Richtung, danke! Ich würde hinzufügen, dass Sie im Spring-Boot nur Folgendes zu application.properties hinzufügen müssen: spring.jackson.serialization.write-date-as-timestamps = false
Joost Lambregts

46

In Spring Boot Web App, mit Jackson und JSR 310 Version "2.8.5"

compile "com.fasterxml.jackson.core:jackson-databind:2.8.5"
runtime "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.5"

Die @JsonFormatArbeiten:

import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;

2
Funktioniert dies für die Deserialisierung? oder nur Serialisierung? Kein Erfolg mit Deserialisierung
Rewolf

7
Ich musste den Deserializer@JsonDeserialize(using= LocalDateDeserializer.class)
Rewolf

1
bei weitem das einfachste!
Antonio

@JsonFormatNur zum Ändern des Ausgabedatenformats. stackoverflow.com/a/53251526/816759 arbeitet perfekt mit @JsonFormat, @JsonDeserialize,@JsonSerialize
Baha

Im Frühjahr Stiefel, sobald Sie die JSR310 Abhängigkeit hinzufügen, alles , was Sie tun müssen , ist hinzuzufügen , spring.jackson.serialization.write-dates-as-timestamps=falsezu Ihrem application.propertiesund es formatiert sie in yyyy-MM-ddautomatisch. Keine Notwendigkeit für@JsonFormat
esfandia

31

Die einfachste Lösung (die auch Deserialisierung und Serialisierung unterstützt) ist

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
private LocalDate dateOfBirth;

Während Sie die folgenden Abhängigkeiten in Ihrem Projekt verwenden.

Maven

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.9.7</version>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jsr310</artifactId>
   <version>2.9.7</version>
</dependency>

Gradle

compile "com.fasterxml.jackson.core:jackson-databind:2.9.7"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7"

Es ist keine zusätzliche Implementierung eines ContextResolver, Serializer oder Deserializer erforderlich.


Genial, bei weitem am einfachsten. Zu Ihrer Information, für alle mit vielen Abhängigkeiten musste ich einige andere Bibliotheken aktualisieren, die Jackson-Anmerkungen enthielten.
Brt

Diese Antwort kommt mir am nächsten, um mein Problem zu beheben. Die Serialisierung funktioniert, aber die Deserialisierung schlägt aufgrund des Musters fehl, das ich mit @JsonFormat verwendet habe (@JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "TT-MM-JJJJ_HH: mm: SS").
fsakiyama

Wenn Sie eine fehlgeschlagene Deserialisierung haben, haben Sie sich höchstwahrscheinlich ObjectMappernicht JavaTimeModuleregistriert. Wenn Ihre ObjectMapper-Instanz über das spring / MessageConverter-Framework bereitgestellt wird. Sie haben etwas Magie getan, um sie zu verkabeln. In anderen Fällen sollte standardmäßig für alle "LocalDate" in POJOregisterModuleLocalDateDeserializer
Dennis C

19

Da LocalDateSerializerwird es standardmäßig in "[Jahr, Monat, Tag]" (ein JSON-Array) anstatt in "Jahr-Monat-Tag" (ein JSON-String) umgewandelt, und da ich kein spezielles ObjectMapperSetup benötigen möchte (können Sie machen LocalDateSerializererzeugen Strings , wenn Sie deaktivieren , SerializationFeature.WRITE_DATES_AS_TIMESTAMPSaber das erfordert eine zusätzliche Einrichtung zu Ihrem ObjectMapper), verwende ich die folgende:

Importe:

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;

Code:

// generates "yyyy-MM-dd" output
@JsonSerialize(using = ToStringSerializer.class)
// handles "yyyy-MM-dd" input just fine (note: "yyyy-M-d" format will not work)
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate localDate;

Und jetzt kann ich new ObjectMapper()meine Objekte einfach ohne spezielle Einstellungen lesen und schreiben.


3
Eine Sache , Ich mag würde hinzuzufügen , ist Datum zu passieren , wie "2018-12-07"statt "2018-12-7"sonst werden Sie eine Fehlermeldung erhalten.
Kid101

1
Richtig, es funktioniert mit dem yyyy-MM-ddFormat (2-stelliger Monat und Tag), nicht mit dem Format yyyy-M-d(1-stelliger Monat oder Tag).
Shadow Man

8

Nur ein Update von Christopher Antwort.

Seit der Version 2.6.0

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.0</version>
</dependency>

Verwenden Sie das JavaTimeModule anstelle von JSR310Module (veraltet).

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {  
    private final ObjectMapper MAPPER;

    public ObjectMapperContextResolver() {
        MAPPER = new ObjectMapper();
        MAPPER.registerModule(new JavaTimeModule());
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return MAPPER;
    }  
}

Gemäß der Dokumentation verwendet das neue JavaTimeModule standardmäßig dieselben Standardeinstellungen für die Serialisierung, bei der KEINE Zeitzonen-IDs verwendet werden und stattdessen nur ISO-8601-kompatible Zeitzonen-Offsets verwendet werden.

Das Verhalten kann mithilfe von SerializationFeature.WRITE_DATES_WITH_ZONE_ID geändert werden


Das hat mir geholfen. In meinem Fall musste ich die MAPPER.registerModule(new JavaTimeModule());Zeile hinzufügen . Damit kann ich LocalDate-Objekte als "2020-02-20" -Format formatieren. Ich brauchte die MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);Leitung nicht für das, wonach ich suchte
Cuga

5

Die folgende Anmerkung hat für mich gut funktioniert.

Keine zusätzlichen Abhängigkeiten erforderlich.

    @JsonProperty("created_at")
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime createdAt;

5
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createdDate;

4

https://stackoverflow.com/a/53251526/1282532 ist der einfachste Weg, um Eigenschaften zu serialisieren / deserialisieren. Ich habe zwei Bedenken hinsichtlich dieses Ansatzes - bis zu einem gewissen Punkt Verstoß gegen das DRY-Prinzip und hohe Kopplung zwischen Pojo und Mapper.

public class Trade {
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate tradeDate;
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate maturityDate;
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate entryDate;
}

Wenn Sie POJO mit mehreren LocalDate-Feldern haben, ist es besser, Mapper anstelle von POJO zu konfigurieren. Es kann so einfach sein wie https://stackoverflow.com/a/35062824/1282532, wenn Sie ISO-8601-Werte verwenden ("2019-01-31").

Wenn Sie ein benutzerdefiniertes Format verwenden müssen, sieht der Code folgendermaßen aus:

ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
mapper.registerModule(javaTimeModule);

Die Logik wird nur einmal geschrieben und kann für mehrere POJO wiederverwendet werden


3

Bisher einfach und am kürzesten:

@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate localDate;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;

Bei Spring boot> = 2.2+ ist keine Abhängigkeit erforderlich


1

Ab 2020 und Jackson 2.10.1 ist kein spezieller Code mehr erforderlich. Es geht nur darum, Jackson zu sagen, was Sie wollen:

ObjectMapper objectMapper = new ObjectMapper();

// Register module that knows how to serialize java.time objects
// Provided by jackson-datatype-jsr310
objectMapper.registerModule(new JavaTimeModule());

// Ask Jackson to serialize dates as String (ISO-8601 by default)
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

Dies wurde bereits in dieser Antwort erwähnt . Ich füge einen Komponententest hinzu, der die Funktionalität überprüft:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.Data;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class LocalDateSerializationTest {

    @Data
    static class TestBean {
        // Accept default ISO-8601 format
        LocalDate birthDate;
        // Use custom format
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
        LocalDate birthDateWithCustomFormat;
    }

    @Test
    void serializeDeserializeTest() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();

        // Register module that knows how to serialize java.time objects
        objectMapper.registerModule(new JavaTimeModule());

        // Ask Jackson to serialize dates as String (ISO-8601 by default)
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        // The JSON string after serialization
        String json = "{\"birthDate\":\"2000-01-02\",\"birthDateWithCustomFormat\":\"03/02/2001\"}";

        // The object after deserialization
        TestBean object = new TestBean();
        object.setBirthDate(LocalDate.of(2000, 1, 2));
        object.setBirthDateWithCustomFormat(LocalDate.of(2001, 2, 3));

        // Assert serialization
        assertEquals(json, objectMapper.writeValueAsString(object));

        // Assert deserialization
        assertEquals(object, objectMapper.readValue(json, TestBean.class));
    }
}

TestBean verwendet Lombok, um die Boilerplate für die Bohne zu generieren.


0

In Konfigurationsklasse definieren LocalDateSerializer und LocalDateDeserializer Klasse und registrieren sie ObjectMapper über JavaTimeModule wie unten:

@Configuration
public class AppConfig
{
@Bean
    public ObjectMapper objectMapper()
    {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(Include.NON_EMPTY);
        //other mapper configs
        // Customize de-serialization


        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());
        mapper.registerModule(javaTimeModule);

        return mapper;
    }

    public class LocalDateSerializer extends JsonSerializer<LocalDate> {
        @Override
        public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(value.format(Constant.DATE_TIME_FORMATTER));
        }
    }

    public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {

        @Override
        public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            return LocalDate.parse(p.getValueAsString(), Constant.DATE_TIME_FORMATTER);
        }
    }
}

0

Wenn Ihre Anfrage ein Objekt wie das folgende enthält:

{
    "year": 1900,
    "month": 1,
    "day": 20
}

Dann können Sie verwenden:

data class DateObject(
    val day: Int,
    val month: Int,
    val year: Int
)
class LocalDateConverter : StdConverter<DateObject, LocalDate>() {
    override fun convert(value: DateObject): LocalDate {
        return value.run { LocalDate.of(year, month, day) }
    }
}

Über dem Feld:

@JsonDeserialize(converter = LocalDateConverter::class)
val dateOfBirth: LocalDate

Der Code ist in Kotlin, aber das würde natürlich auch für Java funktionieren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.