Formato Java 8 LocalDate Jackson

138

Para java.util.Date cuando lo hago

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

luego en la solicitud JSON cuando envío

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

funciona.

¿Cómo debo hacer esto para el campo LocalDate de Java 8 ?

Traté de tener

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

No funcionó.

¿Puede alguien decirme cuál es la forma correcta de hacer esto?

Debajo están las dependencias

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

Respuestas:

106

Nunca pude hacer que esto funcionara de manera simple usando anotaciones. Para conseguir que funcione, he creado una ContextResolverpara ObjectMapper, a continuación, he añadido el JSR310Module( actualización: ahora es JavaTimeModulesu lugar ), junto con una advertencia más, lo cual era la necesidad de fijar la fecha de escritura-como-marca de tiempo en false. Vea más en la documentación para el módulo JSR310 . Aquí hay un ejemplo de lo que usé.

Dependencia

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

Nota: Un problema que enfrenté con esto es que la jackson-annotationversión extraída por otra dependencia, utilizó la versión 2.3.2, que canceló la 2.4 requerida por el jsr310. Lo que sucedió fue que obtuve un NoClassDefFound para ObjectIdResolver, que es una clase 2.4. Así que solo necesitaba alinear las versiones de dependencia incluidas

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

Clase de recursos

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

Prueba

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

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


Consulte también aquí la solución JAXB.

ACTUALIZAR

El JSR310Moduleestá en desuso a partir de la versión 2.7 de Jackson. En su lugar, debe registrar el módulo JavaTimeModule. Sigue siendo la misma dependencia.

Paul Samsotha
fuente
1
Hola Peeskillet, el campo birthDate, se genera como "birthDate": {"year": 0, "month": "Month", "dayOfMonth": 0, "dayOfWeek": "DayOfWeek", "era": {" valor ": 0}," dayOfYear ": 0," leapYear ": falso," monthValue ": 0," chronology ": {" id ":" ", calendarType": ""}} ¿cómo puedo hacerlo? como "birthDate" ???
JAB
Compruebe que se llama a ContextResolver. Agregue una declaración de impresión en el getContextmétodo. Si se llama a este método, no veo una razón para que esto no funcione. Si no se llama, entonces puede ser algo que debe arreglarse con la configuración de la aplicación. Para eso necesitaría ver más de lo que me ha proporcionado. Al igual que la versión Resteasy, las dependencias, la configuración de la aplicación, ya sea web.xml o la subclase de aplicación. Básicamente suficiente para reproducir el problema
Paul Samsotha
ContextResolver no se llama Peeskillet. Lo estoy registrando en web.xml como <context-param> <param-name> resteasy.resources </param-name> <param-value> com.bac.ObjectMapperContextResolver </param-value> </context-param> pregunta actualizada para las dependencias que estoy usando
JAB
Swagger parece ser el problema. Diría que deshabilitarlo, pero al ver esta pregunta hay un problema que se ha archivado, con un conflicto entre el ObjectMapper de Swagger y el intento de usar el suyo. Puede intentar deshabilitar las suyas, y en ContextResolver, configure todas las configuraciones ObjectMappercomo lo hace swagger (puede ver un enlace en la pregunta). No sé, ya que no trabajo mucho con la arrogancia. Pero creo que la arrogancia es el problema principal, por qué no se llama a la resolución de contexto.
Paul Samsotha
Después de más pruebas, la anotación que hace el trabajo. Incluso si tenemos que usar Swagger ObjectMapper, entonces ya configuramos las marcas de tiempo como falsas para nosotros. Entonces esto debería funcionar. Para una mejor ayuda, le sugiero que proporcione un MCVE que demuestre el problema.
Paul Samsotha
95

@JsonSerialize y @JsonDeserialize funcionaron bien para mí. Eliminan la necesidad de importar el módulo jsr310 adicional:

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

Deserializador:

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

}

Serializador:

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));
    }
}
Christopher Yang
fuente
2
Esta es la mejor respuesta para mí. ¡Gracias!
Romeo Jr Maranan
77
Esas clases están incluidas en jackson-datatype-jsr310. No es necesario definirlos manualmente en su proyecto.
NeuroXc
1
Esta solución funcionó para mí, usando los serializadores en jackson-datatype-jsr310.
Dave
2
Esta debería ser la nueva mejor respuesta.
Doctor Parámetro
1
Si usa serializadores y deserializadores en jackson-datatype-jsr310, mejor agregue @JsonFormat (shape = JsonFormat.Shape.STRING) a su campo. Sin este formato, el valor se serializará como [año, mes, día], aunque la deserialización funcionará.
Jian Chen el
74
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

funciona bien para mi

欧阳 世雄
fuente
2
new com.fasterxml.jackson.datatype.jsr310.JSR310Module()para la versión 2.5.4 de Jackson. La clase JavaTimeModule no existe en esta versión.
user3774109
Esta respuesta también funciona para LocalDateTime(jackson 2.9.5). Se requiere 1 dependencia adicional, por lo que mi build.sbt se parece a:"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.5", "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.5"
ruhong
2
Esto debería tener muchos más votos. Simple y efectivo.
mkasberg
Funcionó perfectamente! Gracias.
MAD-HAX
Esto me señaló en la dirección correcta, ¡Gracias! Agregaría que en spring-boot todo lo que necesita hacer es agregar lo siguiente a application.properties: spring.jackson.serialization.write-date-as-timestamps = false
Joost Lambregts
46

En la aplicación web Spring Boot, con Jackson y JSR 310 versión "2.8.5"

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

Los @JsonFormattrabajos:

import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;
Tsolak Barseghyan
fuente
2
¿Funciona esto para la deserialización? o solo serialización? No tener éxito con la deserialización
rewolf
77
Tuve que declarar explícitamente el deserializador@JsonDeserialize(using= LocalDateDeserializer.class)
rewolf
1
de lejos el más simple!
Antonio
@JsonFormatsolo por cambiar el formato de datos de salida. stackoverflow.com/a/53251526/816759 funciona perfecto con @JsonFormat, @JsonDeserialize,@JsonSerialize
Baha
En Spring Boot, una vez que agrega la dependencia JSR310, todo lo que necesita hacer es agregar spring.jackson.serialization.write-dates-as-timestamps=falsea su application.properties, y lo formatea yyyy-MM-ddautomáticamente. No es necesario@JsonFormat
esfandia
31

La solución más simple (que también admite la deserialización y la serialización) es

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;

Mientras usa las siguientes dependencias en su proyecto.

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"

No se requiere una implementación adicional de ContextResolver, Serializer o Deserializer.

Paul Wasilewski
fuente
Brillante, de lejos el más fácil. Para su información para cualquier persona con muchas dependencias, tuve que actualizar algunas otras bibliotecas que incorporaron anotaciones de Jackson.
brt
Esta respuesta es la más cercana que tengo para solucionar mi problema. La serialización funciona, pero la deserialización falla debido al patrón que utilicé con @JsonFormat, creo (@JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-aaaa_HH: mm: SS").
fsakiyama
Si tiene una deserialización fallida, lo más probable es ObjectMapperque no se haya JavaTimeModuleregistrado. Si su instancia de ObjectMapper se proporciona desde el framework spring / MessageConverter. Hicieron algo de magia para conectarlos. En otro caso, debe registerModulehabilitar LocalDateDeserializerpor defecto para todos los "LocalDate" en POJO
Dennis C
19

Ya que LocalDateSerializer convierte en "[año, mes, día]" (una matriz json) en lugar de "año-mes-día" (una cadena json) de forma predeterminada, y dado que no quiero requerir ninguna ObjectMapperconfiguración especial (puede hacer LocalDateSerializergenerar cadenas si lo deshabilita SerializationFeature.WRITE_DATES_AS_TIMESTAMPSpero eso requiere una configuración adicional ObjectMapper), utilizo lo siguiente:

importaciones:

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

código:

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

Y ahora solo puedo usar new ObjectMapper()para leer y escribir mis objetos sin ninguna configuración especial.

Hombre de la sombra
fuente
3
Una cosa que me gustaría agregar es pasar la fecha, ya que en "2018-12-07"lugar de "2018-12-7"eso obtendrás un error.
Kid101
1
Correcto, funciona con yyyy-MM-ddformato (2 dígitos mes y día), no formato yyyy-M-d(1 dígito mes o día).
Shadow Man
8

Solo una actualización de la respuesta de Christopher.

Desde la versión 2.6.0

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

Utilice JavaTimeModule en lugar de JSR310Module (en desuso).

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

De acuerdo con la documentación , el nuevo JavaTimeModule usa la misma configuración estándar para la serialización predeterminada que NO usa Id. De zona horaria, y en su lugar solo utiliza compensaciones de zona horaria compatibles con ISO-8601.

El comportamiento se puede cambiar usando SerializationFeature.WRITE_DATES_WITH_ZONE_ID

bdzzaid
fuente
Esto me ayudo. En mi caso, necesitaba agregar la MAPPER.registerModule(new JavaTimeModule());línea. Me permitió formatear objetos LocalDate como formato "2020-02-20". No necesitaba la MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);línea, para lo que estaba buscando
Cuga
6

La siguiente anotación funcionó bien para mí.

No se necesitan dependencias adicionales.

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

https://stackoverflow.com/a/53251526/1282532 es la forma más simple de serializar / deserializar la propiedad. Tengo dos preocupaciones con respecto a este enfoque: hasta cierto punto, violación del principio DRY y alto acoplamiento entre pojo y 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;
}

En caso de que tenga POJO con múltiples campos LocalDate, es mejor configurar el mapeador en lugar de POJO. Puede ser tan simple como https://stackoverflow.com/a/35062824/1282532 si está utilizando valores ISO-8601 ("2019-01-31")

En caso de que necesite manejar un formato personalizado, el código será así:

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 lógica se escribe solo una vez, se puede reutilizar para múltiples POJO

Pavel
fuente
3

Más simple y más corto hasta ahora:

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

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

no se requiere dependencia con Spring boot> = 2.2+

Anil Bhaskar
fuente
1

A partir de 2020 y Jackson 2.10.1 no hay necesidad de ningún código especial, solo es cuestión de decirle a Jackson lo que quieres:

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

Esto ya se ha mencionado en esta respuesta , estoy agregando una prueba unitaria para verificar la funcionalidad:

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 usa Lombok para generar el repetitivo para el bean.

Bogdan Calmac
fuente
0

En la clase de configuración, defina las clases LocalDateSerializer y LocalDateDeserializer y regístrelas en ObjectMapper a través de JavaTimeModule como se muestra a continuación:

@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);
        }
    }
}
nanosoft
fuente
0

Si su solicitud contiene un objeto como este:

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

Entonces puedes usar:

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

Sobre el campo:

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

El código está en Kotlin, pero esto también funcionaría para Java, por supuesto.

mowwwalker
fuente