serializzare / deserializzare java 8 java.time con il mapper Jackson JSON


221

Come posso utilizzare il mapper Jackson JSON con Java 8 LocalDateTime?

org.codehaus.jackson.map.JsonMappingException: impossibile creare un'istanza del valore di tipo [tipo semplice, classe java.time.LocalDateTime] dalla stringa JSON; nessun metodo costruttore / factory a stringa singola (tramite catena di riferimento: MyDTO ["field1"] -> SubDTO ["date"])


4
sei sicuro di voler mappare un LocalDateTime su JSon? Per quanto ne so, JSon non ha un formato per le date, anche se JavaScript utilizza ISO-8601. Il problema è che LocalDateTime non ha un fuso orario ... quindi, se usi JSON come supporto per inviare informazioni sulla data / ora, potresti avere problemi se il client interpreterà la mancanza del fuso orario come UTC predefinito (o il suo fuso orario). Se è quello che vuoi fare, ovviamente va bene. Ma controlla solo se hai preso in considerazione l'utilizzo di ZonedDateTime invece
arcuri82,

Risposte:


276

Non è necessario utilizzare serializzatori / deserializzatori personalizzati qui. Usa il modulo datetime di jackson-modules-java8 :

Modulo tipo di dati per fare in modo che Jackson riconosca i tipi di dati API Java 8 Date & Time (JSR-310).

Questo modulo aggiunge il supporto per alcune classi:

  • Durata
  • Immediato
  • LocalDateTime
  • LocalDate
  • Ora locale
  • Mese giorno
  • OffsetDateTime
  • OffsetTime
  • Periodo
  • Anno
  • Anno mese
  • ZonedDateTime
  • IDArea
  • ZoneOffset

59
Oh si! Pensavo che non funzionasse perché non mi rendevo conto che si deve fare una di queste personalizzazioni all'oggetto mapper: registerModule(new JSR310Module())o findAndRegisterModules(). Vedi github.com/FasterXML/jackson-datatype-jsr310 ed ecco come personalizzare il tuo oggetto mapper se usi il framework Spring: stackoverflow.com/questions/7854030/…
Alexander Taylor,

10
Non funziona con quello più semplice con cui ho provato,OffsetDateTime @Test public void testJacksonOffsetDateTimeDeserializer() throws IOException { ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); String json = "\"2015-10-20T11:00:00-8:30\""; mapper.readValue(json, OffsetDateTime.class); }
Abhijit Sarkar,

9
Se si utilizza il recente framework Spring non è più necessario personalizzarsi, poiché i noti moduli Jackson come questo ora vengono automaticamente registrati se rilevati sul percorso di classe: spring.io/blog/2014/12/02/…
vorburger,

37
JSR310Module è già un po 'obsoleto, invece usa JavaTimeModule
Jacob Eckel

17
@MattBall Vorrei suggerire aggiungendo che, oltre a utilizzare jackson-tipo di dati-jsr310, è necessario registrare il JavaTimeModule con il mapper oggetto: objectMapper.registerModule(new JavaTimeModule());. Sia sulla serializzazione che sulla deserializzazione.
Grandammiraglio

76

Aggiornamento: lasciare questa risposta per motivi storici, ma non lo consiglio. Si prega di consultare la risposta accettata sopra.

Di 'a Jackson di mappare usando le tue classi [de] di serializzazione personalizzate:

@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime ignoreUntil;

fornire lezioni personalizzate:

public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
    @Override
    public void serialize(LocalDateTime arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
        arg1.writeString(arg0.toString());
    }
}

public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
    @Override
    public LocalDateTime deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException {
        return LocalDateTime.parse(arg0.getText());
    }
}

fatto casuale: se annido sopra le classi e non le rendo statiche, il messaggio di errore è strano: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported


al momento della stesura di questo documento, l'unico vantaggio qui rispetto ad altre risposte non dipende da FasterXML, qualunque esso sia.
Alexander Taylor,


4
Oh, capisco. così tante voci pom antiche nel progetto a cui sto lavorando. quindi niente più org.codehaus, ora è tutto com.fasterxml. Grazie!
Alexander Taylor,

2
Inoltre non sono stato in grado di utilizzare quello integrato perché dovevo passare il formattatore ISO_DATE_TIME. Non sono sicuro se è possibile farlo quando si utilizza il modulo JSR310.
isaac.hazan,

Usandolo con Spring ho dovuto creare un bean di entrambi LocalDateTimeSerializereLocalDateTimeDeserializer
BenR

53

Se si utilizza la classe ObjectMapper di fastxml, per impostazione predefinita ObjectMapper non comprende la classe LocalDateTime, quindi è necessario aggiungere un'altra dipendenza nel gradle / maven:

compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.7.3'

Ora è necessario registrare il supporto del tipo di dati offerto da questa libreria nell'oggetto objectmapper, questo può essere fatto seguendo:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();

Ora, nel tuo jsonString, puoi facilmente inserire il tuo campo java.LocalDateTime come segue:

{
    "user_id": 1,
    "score": 9,
    "date_time": "2016-05-28T17:39:44.937"
}

In questo modo, la conversione del file Json in oggetto Java funzionerà correttamente, è possibile leggere il file come segue:

objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
            });

4
findAndRegisterModules()era un pezzo critico che mi mancava quando costruivo unObjectMapper
Xaero Degreaz il

2
Che dire di Instant?
powder366

1
Ho anche aggiunto questa riga: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Janet

questo è il codice completo necessario: ObjectMapper objectMapper = new ObjectMapper (); objectMapper.findAndRegisterModules (); objectMapper.configure (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
khush,

42

Questa dipendenza maven risolverà il tuo problema:

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

Una cosa che ho faticato è che per il fuso orario ZonedDateTime essere cambiato in GMT durante la deserializzazione. Si è scoperto che, per impostazione predefinita, Jackson lo sostituisce con uno dal contesto. Per mantenere la zona è necessario disabilitare questa "funzione"

Jackson2ObjectMapperBuilder.json()
    .featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)

5
Mille grazie per DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE suggerimento.
Andrei Urvantcev,

5
Oltre alla disabilitazione, DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONEho dovuto disabilitare anche SerializationFeature.WRITE_DATES_AS_TIMESTAMPStutto per iniziare a funzionare come dovrebbe.
Matt Watson,

16

Ho avuto un problema simile durante l'utilizzo di Spring Boot . Con Spring boot 1.5.1.RELEASE tutto quello che dovevo fare è aggiungere dipendenza:

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

7

Se stai usando Jersey, devi aggiungere la dipendenza Maven (jackson-datatype-jsr310) come suggerito dagli altri e registrare l'istanza del mappatore oggetti in questo modo:

@Provider
public class JacksonObjectMapper implements ContextResolver<ObjectMapper> {

  final ObjectMapper defaultObjectMapper;

  public JacksonObjectMapper() {
    defaultObjectMapper = createDefaultMapper();
  }

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

  private static ObjectMapper createDefaultMapper() {
    final ObjectMapper mapper = new ObjectMapper();    
    mapper.registerModule(new JavaTimeModule());
    return mapper;
  }
}

Quando registri Jackson nelle tue risorse, devi aggiungere questo mapper in questo modo:

final ResourceConfig rc = new ResourceConfig().packages("<your package>");
rc
  .register(JacksonObjectMapper.class)
  .register(JacksonJaxbJsonProvider.class);

6

Se non puoi usare jackson-modules-java8per qualsiasi motivo puoi (de-) serializzare il campo istantaneo come longusando @JsonIgnoree @JsonGetter& @JsonSetter:

public class MyBean {

    private Instant time = Instant.now();

    @JsonIgnore
    public Instant getTime() {
        return this.time;
    }

    public void setTime(Instant time) {
        this.time = time;
    }

    @JsonGetter
    private long getEpochTime() {
        return this.time.toEpochMilli();
    }

    @JsonSetter
    private void setEpochTime(long time) {
        this.time = Instant.ofEpochMilli(time);
    }
}

Esempio:

@Test
public void testJsonTime() throws Exception {
    String json = new ObjectMapper().writeValueAsString(new MyBean());
    System.out.println(json);
    MyBean myBean = new ObjectMapper().readValue(json, MyBean.class);
    System.out.println(myBean.getTime());
}

i rendimenti

{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z

Questo è un metodo molto pulito e consente agli utenti della tua classe di usare quello che vogliono.
Ashhar Hasan,

3

Uso questo formato temporale: "{birthDate": "2018-05-24T13:56:13Z}"per deserializzare da json a java.time.Instant (vedi screenshot)

inserisci qui la descrizione dell'immagine


3

Questo è solo un esempio di come usarlo in un test unit che ho hackerato per eseguire il debug di questo problema. Gli ingredienti chiave sono

  • mapper.registerModule(new JavaTimeModule());
  • maven dipendenza <artifactId>jackson-datatype-jsr310</artifactId>

Codice:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.IOException;
import java.io.Serializable;
import java.time.Instant;

class Mumu implements Serializable {
    private Instant from;
    private String text;

    Mumu(Instant from, String text) {
        this.from = from;
        this.text = text;
    }

    public Mumu() {
    }

    public Instant getFrom() {
        return from;
    }

    public String getText() {
        return text;
    }

    @Override
    public String toString() {
        return "Mumu{" +
                "from=" + from +
                ", text='" + text + '\'' +
                '}';
    }
}
public class Scratch {


    @Test
    public void JacksonInstant() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());

        Mumu before = new Mumu(Instant.now(), "before");
        String jsonInString = mapper.writeValueAsString(before);


        System.out.println("-- BEFORE --");
        System.out.println(before);
        System.out.println(jsonInString);

        Mumu after = mapper.readValue(jsonInString, Mumu.class);
        System.out.println("-- AFTER --");
        System.out.println(after);

        Assert.assertEquals(after.toString(), before.toString());
    }

}

2

Puoi impostarlo nel tuo application.ymlfile per risolvere Instant time, che è l'API Date in java8:

spring.jackson.serialization.write-dates-as-timestamps=false

2

Se stai usando Spring boot e hai questo problema con OffsetDateTime, allora devi usare registerModules come indicato sopra da @greperror (con risposta 28 maggio 16 alle 13:04) ma nota che c'è una differenza. Non è necessario aggiungere la dipendenza menzionata poiché suppongo che lo stivale a molla lo abbia già. Stavo avendo questo problema con Spring boot e ha funzionato per me senza aggiungere questa dipendenza.


1

Per coloro che usano Spring Boot 2.x

Non è necessario eseguire alcuna delle operazioni precedenti: Java 8 LocalDateTime è serializzato / deserializzato immediatamente. Ho dovuto fare tutto quanto sopra in 1.x, ma con Boot 2.x, funziona perfettamente.

Vedi anche questo riferimento Formato JSON Java 8 LocalDateTime in Spring Boot


1

Se qualcuno ha problemi durante l'utilizzo SpringBootqui è come ho risolto il problema senza aggiungere nuova dipendenza.

A Spring 2.1.3Jackson si aspetta che la stringa di date 2019-05-21T07:37:11.000in questo yyyy-MM-dd HH:mm:ss.SSSformato venga serializzata LocalDateTime. Assicurarsi che la stringa della data separi la data e l'ora con Tnon con space. secondi ( ss) e millisecondi ( SSS) potrebbero essere omessi.

@JsonProperty("last_charge_date")
public LocalDateTime lastChargeDate;

1

Se si utilizza Jackson Serializer , ecco un modo per utilizzare i moduli data:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.apache.kafka.common.serialization.Serializer;

public class JacksonSerializer<T> implements Serializer<T> {

    private final ObjectMapper mapper = new ObjectMapper()
            .registerModule(new ParameterNamesModule())
            .registerModule(new Jdk8Module())
            .registerModule(new JavaTimeModule());

    @Override
    public byte[] serialize(String s, T object) {
        try {
            return mapper.writeValueAsBytes(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

0

Se si considera l'utilizzo di Fastjson, è possibile risolvere il problema, prendere nota della versione

 <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.56</version>
 </dependency>

0

Se si riscontra questo problema a causa degli strumenti Java GraphQL e del tentativo di eseguire il marshalling di un Java Instantda una stringa di data, è necessario impostare SchemaParser per utilizzare un ObjectMapper con determinate configurazioni:

Nella classe GraphQLSchemaBuilder, iniettare ObjectMapper e aggiungere questi moduli:

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

e aggiungilo alle opzioni:

final SchemaParserOptions options = SchemaParserOptions.newOptions()
            .objectMapperProvider(fieldDefinition -> objectMapper)
            .typeDefinitionFactory(new YourTypeDefinitionFactory())
            .build();

Vedi https://github.com/graphql-java-kickstart/graphql-spring-boot/issues/32

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.