serializar / deserializar java 8 java.time con el mapeador Jackson JSON

221

¿Cómo uso el mapeador Jackson JSON con Java 8 LocalDateTime?

org.codehaus.jackson.map.JsonMappingException: no se puede crear una instancia del valor de tipo [tipo simple, clase java.time.LocalDateTime] de la cadena JSON; sin constructor de cadena única / método de fábrica (a través de la cadena de referencia: MyDTO ["field1"] -> SubDTO ["date"])

Alexander Taylor
fuente
44
¿estás seguro de que quieres asignar un LocalDateTime a JSon? Hasta donde yo sé, JSon no tiene un formato para las fechas, aunque JavaScript usa ISO-8601. El problema es que LocalDateTime no tiene una zona horaria ... por lo tanto, si usa JSON como medio para enviar información de fecha / hora, podría tener problemas si el cliente interpretará la falta de zona horaria como UTC predeterminado (o su propio zona horaria). Si eso es lo que quieres hacer, por supuesto que está bien. Pero solo verifique si ha considerado usar ZonedDateTime en su lugar
arcuri82

Respuestas:

276

No es necesario usar serializadores / deserializadores personalizados aquí. Utilice el módulo de fecha y hora de jackson-modules-java8 :

Módulo de tipo de datos para que Jackson reconozca los tipos de datos de API de fecha y hora de Java 8 (JSR-310).

Este módulo agrega soporte para bastantes clases:

  • Duración
  • Instante
  • LocalDateTime
  • Fecha local
  • Hora local
  • Mes dia
  • OffsetDateTime
  • OffsetTime
  • Período
  • Año
  • Año mes
  • ZonedDateTime
  • ZoneId
  • ZoneOffset
Matt Ball
fuente
59
¡Oh si! Pensé que esto no estaba funcionando porque no me di cuenta de que uno debe hacer una de estas personalizaciones para el objeto del mapeador: registerModule(new JSR310Module())o findAndRegisterModules(). Consulte github.com/FasterXML/jackson-datatype-jsr310 y aquí le mostramos cómo personalizar su objeto de mapeador si usa Spring framework: stackoverflow.com/questions/7854030/…
Alexander Taylor
10
No funciona con el más simple con el que probé,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
99
Si usa Spring Framework reciente ya no es necesario personalizarlo, ya que los módulos Jackson conocidos como este ahora se registran automáticamente si se detectan en el classpath: spring.io/blog/2014/12/02/…
vorburger
37
JSR310Module ya está un poco desactualizado, en su lugar use JavaTimeModule
Jacob Eckel el
17
@MattBall Yo sugeriría añadiendo que, además de utilizar Jackson-jsr310 tipo de datos, es necesario registrar el JavaTimeModule con su mapeador objeto: objectMapper.registerModule(new JavaTimeModule());. Tanto en la serialización como en la deserialización.
GrandAdmiral
76

Actualización: Dejando esta respuesta por razones históricas, pero no la recomiendo. Por favor vea la respuesta aceptada arriba.

Dígale a Jackson que asigne utilizando sus clases de serialización [de] personalizadas:

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

proporcionar clases personalizadas:

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

hecho aleatorio: si anido las clases anteriores y no las hago estáticas, el mensaje de error es extraño: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported

Alexander Taylor
fuente
a partir de este escrito, la única ventaja aquí sobre otra respuesta no depende de FasterXML, sea lo que sea.
Alexander Taylor
44
FasterXML es Jackson. biggerxml.com github.com/fasterxml/jackson
Matt Ball
44
Oh ya veo. Tantas entradas antiguas de pom en el proyecto en el que estoy trabajando. así que no más org.codehaus, todo es com.fasterxml ahora. ¡Gracias!
Alexander Taylor
2
Además, no pude usar el incorporado porque necesitaba pasar el formateador ISO_DATE_TIME. No estoy seguro de si es posible hacer eso al usar el módulo JSR310.
isaac.hazan
Usando esto con Spring tuve que crear un bean de ambos LocalDateTimeSerializeryLocalDateTimeDeserializer
BenR
53

Si está utilizando la clase ObjectMapper de rapidxml, por defecto ObjectMapper no entiende la clase LocalDateTime, por lo tanto, debe agregar otra dependencia en su gradle / maven:

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

Ahora debe registrar el soporte de tipo de datos que ofrece esta biblioteca en su objeto objectmapper, esto se puede hacer de la siguiente manera:

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

Ahora, en su jsonString, puede poner fácilmente su campo java.LocalDateTime de la siguiente manera:

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

Al hacer todo esto, su conversión de archivos Json a objetos Java funcionará bien, puede leer el archivo de la siguiente manera:

objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
            });
greperror
fuente
44
findAndRegisterModules()fue una pieza crítica que me faltaba al construir unObjectMapper
Xaero Degreaz
2
¿Qué pasa con Instant?
powder366
1
También agregué esta línea: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Janet
este es el código completo necesario: ObjectMapper objectMapper = new ObjectMapper (); objectMapper.findAndRegisterModules (); objectMapper.configure (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Khush
42

Esta dependencia de Maven resolverá su problema:

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

Una cosa que me ha costado es que la zona horaria ZonedDateTime se cambie a GMT durante la deserialización. Resultó que, por defecto, Jackson lo reemplaza con uno del contexto. Para mantener la zona, uno debe deshabilitar esta 'característica'

Jackson2ObjectMapperBuilder.json()
    .featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
tomo
fuente
55
Muchas gracias por DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE.
Andrei Urvantcev
55
Además de deshabilitar DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, también tuve que deshabilitar SerializationFeature.WRITE_DATES_AS_TIMESTAMPSpara que todo comience a funcionar como debería.
Matt Watson el
16

Tuve un problema similar al usar Spring boot . Con Spring boot 1.5.1.RELEASE todo lo que tuve que hacer es agregar dependencia:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Witold Kaczurba
fuente
7

Si está utilizando Jersey, entonces necesita agregar la dependencia de Maven (jackson-datatype-jsr310) como lo sugirieron los demás y registrar su instancia de mapeador de objetos de la siguiente manera:

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

Al registrar a Jackson en sus recursos, debe agregar este mapeador de la siguiente manera:

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

Si no se puede utilizar jackson-modules-java8por cualquier razón se puede (des) serializar el campo instantánea como longel uso @JsonIgnorey @JsonGetterY @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);
    }
}

Ejemplo:

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

rendimientos

{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z
Tú haces
fuente
Este es un método muy limpio y permite a los usuarios de su clase usar lo que quieran.
Ashhar Hasan
3

Utilizo este formato de hora: "{birthDate": "2018-05-24T13:56:13Z}"para deserializar de json a java.time.Instant (ver captura de pantalla)

ingrese la descripción de la imagen aquí

Taras Melnyk
fuente
3

Este es solo un ejemplo de cómo usarlo en una prueba unitaria que pirateé para depurar este problema. Los ingredientes clave son

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

Código:

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

}
Mircea Stanciu
fuente
2

Puede configurar esto en su application.ymlarchivo para resolver el tiempo instantáneo, que es la API de fecha en java8:

spring.jackson.serialization.write-dates-as-timestamps=false
Janki
fuente
2

Si está usando Spring Boot y tiene este problema con OffsetDateTime, entonces necesita usar el registerModules como respondió anteriormente @greperror (respondió el 28 de mayo de 16 a las 13:04) pero tenga en cuenta que hay una diferencia. La dependencia mencionada no necesita ser agregada, ya que supongo que Spring Boot ya la tiene. Estaba teniendo este problema con Spring boot y funcionó para mí sin agregar esta dependencia.

VC2019
fuente
1

Para aquellos que usan Spring Boot 2.x

No es necesario hacer nada de lo anterior: Java 8 LocalDateTime está serializado / deserializado de fábrica. Tuve que hacer todo lo anterior en 1.x, pero con Boot 2.x, funciona a la perfección.

Consulte también esta referencia JSON Java 8 LocalDateTime format en Spring Boot

HopeKing
fuente
1

Si alguien que tiene problemas al usar SpringBootaquí es cómo solucioné el problema sin agregar una nueva dependencia.

En Spring 2.1.3Jackson espera que la cadena de fecha 2019-05-21T07:37:11.000en este yyyy-MM-dd HH:mm:ss.SSSformato se des-serialice LocalDateTime. Asegúrese de que la cadena de fecha separe la fecha y la hora con Tno con space. segundos ( ss) y milisegundos ( SSS) podrían omitirse.

@JsonProperty("last_charge_date")
public LocalDateTime lastChargeDate;
Muhammad Usman
fuente
1

Si está utilizando Jackson Serializer , esta es una forma de usar los módulos de fecha:

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;
    }
}
edubriguenti
fuente
0

Si considera usar fastjson, puede resolver su problema, tenga en cuenta la versión

 <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.56</version>
 </dependency>
byte mamba
fuente
0

Si tiene este problema debido a GraphQL Java Tools y está tratando de ordenar un Java a Instantpartir de una cadena de fecha, debe configurar su SchemaParser para usar un ObjectMapper con ciertas configuraciones:

En su clase GraphQLSchemaBuilder, inyecte ObjectMapper y agregue estos módulos:

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

y agregarlo a las opciones:

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

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

Janac Meena
fuente