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.