Formato Java 8 LocalDate Jackson


138

Per java.util.Date quando lo faccio

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

quindi nella richiesta JSON quando invio

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

Funziona.

Come devo fare questo per il campo LocalDate di Java 8 ??

Ho provato ad avere

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

Non ha funzionato

Qualcuno può farmi sapere qual è il modo giusto per farlo ...

Di seguito sono le dipendenze

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

Risposte:


106

Non sono mai stato in grado di farlo funzionare semplicemente usando le annotazioni. Per farlo funzionare, ho creato un ContextResolverfor ObjectMapper, quindi ho aggiunto il JSR310Module( update: ora JavaTimeModuleinvece è ), insieme ad un altro avvertimento, che era la necessità di impostare write-date-as-timestamp su false. Vedi di più nella documentazione per il modulo JSR310 . Ecco un esempio di quello che ho usato.

Dipendenza

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

Nota: un problema che ho dovuto affrontare è che la jackson-annotationversione estratta da un'altra dipendenza, utilizzata la versione 2.3.2, che ha annullato la 2.4 richiesta da jsr310. Quello che è successo è che ho ottenuto un NoClassDefFound ObjectIdResolver, che è una classe 2.4. Quindi avevo solo bisogno di allineare le versioni di dipendenza incluse

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

Classe di risorse

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

Test

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

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


Vedi anche qui per la soluzione JAXB.

AGGIORNARE

Il JSR310Moduleè deprecato a partire dalla versione 2.7 di Jackson. Invece, dovresti registrare il modulo JavaTimeModule. È ancora la stessa dipendenza.


1
Ciao Peeskillet, il campo birthDate, viene generato come "birthDate": {"anno": 0, "mese": "Mese", "dayOfMonth": 0, "dayOfWeek": "DayOfWeek", "era": {" value ": 0}," dayOfYear ": 0," leapYear ": false," monthValue ": 0," chronology ": {" id ":" "," calendarType ":" "}} come posso realizzarlo come "birthDate" ???
JAB

Verificare che venga chiamato ContextResolver. Aggiungi un'istruzione print nel getContextmetodo Se viene chiamato questo metodo, non vedo un motivo per cui questo non funziona. Se non viene chiamato, potrebbe essere qualcosa che deve essere risolto con la configurazione dell'app. Per questo avrei bisogno di vedere più di quello che hai fornito. Come la versione Resteasy, le dipendenze, la configurazione dell'app sia web.xml o la sottoclasse dell'applicazione. Fondamentalmente abbastanza per riprodurre il problema
Paul Samsotha,

ContextResolver non viene chiamato Peeskillet. Lo sto registrando di nuovo in web.xml come <context-param> <param-name> resteasy.resources </param-name> <param-value> com.bac.ObjectMapperContextResolver </param-value> </context-param> domanda aggiornata per le dipendenze che sto usando
JAB

Swagger sembra essere il problema. Direi di disabilitarlo, ma vedendo da questa domanda c'è un problema che è stato archiviato, con un conflitto tra ObjectMapper di Swagger e il tentativo di utilizzare il proprio. Puoi provare a disabilitarli e, nel ContextResolver, impostare tutte le configurazioni su ObjectMappercome fa lo swagger (puoi vedere un link nella domanda). Non lo so perché non lavoro molto con Swagger. Ma penso che il problema principale sia lo swagger, perché non viene chiamato il contextresolver.
Paul Samsotha,

Dopo ulteriori test, L'annotazione fa lavoro. Anche se noi abbiamo di utilizzare Swagger ObjectMapper, quindi già configurare i timestamp come false per noi. Quindi dovrebbe funzionare. Per un aiuto migliore, ti consiglio vivamente di fornire un MCVE che dimostri il problema.
Paul Samsotha,

95

@JsonSerialize e @JsonDeserialize hanno funzionato bene per me. Eliminano la necessità di importare il modulo jsr310 aggiuntivo:

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

deserializzatore:

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
Questa è la risposta migliore per me. Grazie!
Romeo Jr Maranan,

7
Queste classi sono incluse in jackson-datatype-jsr310. Non è necessario definirli manualmente nel progetto.
NeuroXc,

1
Questa soluzione ha funzionato per me, usando i serializzatori in jackson-datatype-jsr310.
dave

2
Questa dovrebbe essere la nuova migliore risposta.
Doctor Parameter,

1
Se usi serializzatori e deserializzatori in jackson-datatype-jsr310, aggiungi meglio @JsonFormat (shape = JsonFormat.Shape.STRING) al tuo campo. Senza questo formato, il valore verrà serializzato come [anno, mese, giorno], sebbene la deserializzazione funzionerà.
Jian Chen

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

funziona bene per me.


2
new com.fasterxml.jackson.datatype.jsr310.JSR310Module()per la versione 2.5.4 di Jackson. La classe JavaTimeModule non esiste in questa versione.
user3774109,

Questa risposta funziona anche per LocalDateTime(jackson 2.9.5). 1 dipendenza aggiuntiva richiesta, quindi il mio build.sbt assomiglia a:"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.5", "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.5"
ruhong

2
Questo dovrebbe avere molti più voti. Semplice ed efficace
mkasberg

Ha funzionato perfettamente! Grazie.
MAD-HAX

Questo mi ha indicato nella giusta direzione, grazie! Vorrei aggiungere che in avvio di primavera tutto ciò che devi fare è aggiungere quanto segue a application.properties: spring.jackson.serialization.write-date-as-timestamps = false
Joost Lambregts,

46

Nella primavera del Boot web app, con Jackson e JSR 310 versione "2.8.5"

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

Le @JsonFormatopere:

import com.fasterxml.jackson.annotation.JsonFormat;

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

2
Funziona per la deserializzazione? o solo serializzazione? Non avendo successo con la deserializzazione
rewolf

7
Ho dovuto dichiarare esplicitamente il deserializzatore@JsonDeserialize(using= LocalDateDeserializer.class)
rewolf il

1
di gran lunga il più semplice!
Antonio,

@JsonFormatsolo per modificare il formato dei dati di output. stackoverflow.com/a/53251526/816759 funziona perfettamente con @JsonFormat, @JsonDeserialize,@JsonSerialize
Baha

In Spring Boot, una volta aggiunta la dipendenza JSR310, tutto ciò che devi fare è aggiungere spring.jackson.serialization.write-dates-as-timestamps=falseal tuo application.propertiese lo formatta yyyy-MM-ddautomaticamente. Non c'è bisogno di@JsonFormat
esfandia

31

La soluzione più semplice (che supporta anche la deserializzazione e la serializzazione) è

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;

Durante l'utilizzo delle seguenti dipendenze nel progetto.

Esperto di

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

Non è richiesta alcuna implementazione aggiuntiva di un ContextResolver, Serializer o Deserializer.


Brillante, di gran lunga il più semplice. Cordiali saluti per chiunque abbia molte dipendenze, ho dovuto aggiornare alcune altre librerie che incorporavano le annotazioni di Jackson.
Brt

Questa risposta è la più vicina che ho avuto per risolvere il mio problema. La serializzazione funziona, ma la deserializzazione non riesce a causa del modello che ho usato con @JsonFormat penso (@JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy_HH: mm: SS").
fsakiyama

Se hai una deserializzazione fallita, molto probabilmente ObjectMappernon sei JavaTimeModuleregistrato. Se l'istanza di ObjectMapper viene fornita dal framework spring / MessageConverter. Hanno fatto un po 'di magia per collegarli. In altri casi, dovrebbe registerModuleabilitare LocalDateDeserializerper impostazione predefinita per tutti "LocalDate" in POJO
Dennis C

19

Dal momento che lo LocalDateSerializertrasforma in "[anno, mese, giorno]" (un array json) anziché "anno-mese-giorno" (una stringa json) per impostazione predefinita, e poiché non voglio richiedere alcuna ObjectMapperconfigurazione speciale (puoi fai LocalDateSerializergenerare stringhe se disabiliti SerializationFeature.WRITE_DATES_AS_TIMESTAMPSma ciò richiede una configurazione aggiuntiva per il tuo ObjectMapper), io uso il seguente:

importazioni:

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

codice:

// 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;

E ora posso semplicemente usare new ObjectMapper()per leggere e scrivere i miei oggetti senza alcuna configurazione speciale.


3
Una cosa che vorrei aggiungere è passare la data in quanto "2018-12-07"invece di "2018-12-7"ottenere un errore.
Kid101,

1
Corretto, funziona con il formato yyyy-MM-dd(mese e giorno a 2 cifre), non con il formato yyyy-M-d(mese o giorno a 1 cifra).
Shadow Man il

8

Solo un aggiornamento della risposta di Christopher.

Dalla versione 2.6.0

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

Utilizzare JavaTimeModule invece di JSR310Module (obsoleto).

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

Secondo la documentazione , il nuovo JavaTimeModule utilizza le stesse impostazioni standard per impostazione predefinita alla serializzazione che NON utilizza ID fuso orario e utilizza solo offset del fuso orario conformi ISO-8601.

Il comportamento può essere modificato utilizzando SerializationFeature.WRITE_DATES_WITH_ZONE_ID


Questo mi ha aiutato. Nel mio caso, avevo bisogno di aggiungere la MAPPER.registerModule(new JavaTimeModule());linea. Mi consente di formattare gli oggetti LocalDate come formato "2020-02-20". Non avevo bisogno della MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);linea, per quello che stavo cercando
Cuga,

6

La seguente annotazione ha funzionato bene per me.

Non sono necessarie dipendenze extra.

    @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 è il modo più semplice per serializzare / deserializzare la proprietà. Ho due preoccupazioni riguardo a questo approccio: fino ad un certo punto la violazione del principio DRY e l'elevato accoppiamento tra pojo e 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;
}

Nel caso in cui tu abbia POJO con più campi LocalDate è meglio configurare mapper invece di POJO. Può essere semplice come https://stackoverflow.com/a/35062824/1282532 se si utilizzano i valori ISO-8601 ("2019-01-31")

Nel caso in cui sia necessario gestire un formato personalizzato, il codice sarà così:

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

La logica è scritta una sola volta, può essere riutilizzata per più POJO


3

Più semplice e più breve finora:

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

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

nessuna dipendenza richiesta con Spring boot> = 2.2+


1

A partire dal 2020 e Jackson 2.10.1 non è necessario alcun codice speciale, è solo una questione di dire a Jackson quello che vuoi:

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

Questo è già stato menzionato in questa risposta , sto aggiungendo un test unitario per verificare la funzionalità:

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 utilizza Lombok per generare il boilerplate per il bean.


0

Nella classe di configurazione definisci le classi LocalDateSerializer e LocalDateDeserializer e registrale su ObjectMapper tramite JavaTimeModule come di seguito:

@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

Se la tua richiesta contiene un oggetto come questo:

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

Quindi puoi usare:

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

Sopra il campo:

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

Il codice è in Kotlin, ma ovviamente funzionerebbe anche per Java.

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.