Formato Java 8 LocalDate Jackson

138

Para java.util.Date quando eu faço

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

então na solicitação JSON quando eu enviar

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

funciona.

Como devo fazer isso no campo LocalDate do Java 8 ?

Eu tentei ter

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

Não deu certo.

Alguém pode me informar qual é a maneira correta de fazer isso ..

Abaixo estão dependências

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

Respostas:

106

Eu nunca consegui fazer isso funcionar de maneira simples usando anotações. Para obtê-lo para o trabalho, eu criei um ContextResolverpara ObjectMapper, então eu adicionei o JSR310Module( atualização: agora é JavaTimeModulevez ), juntamente com mais uma ressalva, que foi a necessidade de set write-data-as-timestamp como falsa. Veja mais na documentação do módulo JSR310 . Aqui está um exemplo do que eu usei.

Dependência

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

Nota: Um problema que enfrentei foi que a jackson-annotationversão extraída por outra dependência, usou a versão 2.3.2, que cancelou a 2.4 exigida pelo jsr310. O que aconteceu foi que eu recebi um NoClassDefFound ObjectIdResolver, que é uma classe 2.4. Então, eu só precisava alinhar as versões de dependência incluídas

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

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

Teste

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


Veja também aqui a solução JAXB.

ATUALIZAR

O JSR310Modulefoi descontinuado a partir da versão 2.7 do Jackson. Em vez disso, você deve registrar o módulo JavaTimeModule. Ainda é a mesma dependência.

Paul Samsotha
fonte
1
Olá, Peeskillet, o campo birthDate, está sendo gerado como "birthDate": {"year": 0, "month": "Month", "dayOfMonth": 0, "dayOfWeek": "DayOfWeek", "era": {" valor ": 0}," dayOfYear ": 0," leapYear ": false," monthValue ": 0," cronologia ": {" id ":" "," calendarType ":" "}} como posso fazer isso apenas como "data de nascimento" ???
JAB
Verifique se o ContextResolver é chamado. Adicione uma declaração de impressão no getContextmétodo Se esse método for chamado, não vejo uma razão para isso não funcionar. Se não for chamado, pode ser algo que precise ser corrigido com a configuração do aplicativo. Para isso eu precisaria ver mais do que você forneceu. Como a versão Resteasy, dependências, a configuração do aplicativo web.xml ou a subclasse Aplicativo. Basicamente o suficiente para reproduzir o problema
Paul Samsotha
O ContextResolver não está sendo chamado de Peeskillet. Estou registrando-o no web.xml como <context-param> <param-name> resteasy.resources </param-name> <param-value> com.bac.ObjectMapperContextResolver </param-value> </context-param> pergunta actualizado para dependências Eu estou usando
JAB
Swagger parece ser o problema. Eu diria para desativá-lo, mas, a partir desta pergunta, há um problema que foi registrado, com um conflito entre o ObjectMapper do Swagger e a tentativa de usar o seu próprio. Você pode tentar desativar os deles e, no ContextResolver, definir todas as configurações da ObjectMappermesma forma que o arrogante (você pode ver um link na pergunta). Não sei, pois não trabalho muito com arrogância. Mas acho que a arrogância é o principal problema, por que o resolvedor de contexto não está sendo chamado.
Paul Samsotha
Depois de mais testes, a anotação faz trabalho. Mesmo que tenhamos que usar o Swagger ObjectMapper, já configure os carimbos de hora como falsos para nós. Então isso deve funcionar. Para uma melhor ajuda, sugiro fortemente que você forneça um MCVE que demonstre o problema.
Paul Samsotha
95

@JsonSerialize e @JsonDeserialize funcionaram bem para mim. Eles eliminam a necessidade de importar o módulo jsr310 adicional:

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

Desserializador:

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
fonte
2
Esta é a melhor resposta para mim. Obrigado!
Romeo Jr Maranan
7
Essas classes estão incluídas no jackson-datatype-jsr310. Não há necessidade de defini-los manualmente em seu projeto.
NeuroXc
1
Esta solução funcionou para mim, usando os serializadores no jackson-datatype-jsr310.
dave
2
Essa deve ser a nova melhor resposta.
Doctor Parameter
1
Se você usar serializadores e desserializadores em jackson-datatype-jsr310, adicione melhor @JsonFormat (shape = JsonFormat.Shape.STRING) ao seu campo. Sem esse formato, o valor será serializado como [ano, mês, dia], embora a desserialização funcione.
Jian Chen
74
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

funciona bem para mim.

欧阳 世雄
fonte
2
new com.fasterxml.jackson.datatype.jsr310.JSR310Module()para a versão 2.5.4 do Jackson. A classe JavaTimeModule não existe nesta versão.
user3774109
Esta resposta também funciona para LocalDateTime (jackson 2.9.5). É necessária uma dependência adicional, para que o meu build.sbt se pareça com:"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.5", "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.5"
ruhong
2
Isso deveria ter muito mais votos. Simples e eficaz.
Mkasberg
Funcionou perfeitamente! Obrigado.
MAD-HAX 08/04/19
Isso me apontou na direção certa, obrigado! Eu acrescentaria que no spring-boot tudo o que você precisa fazer é adicionar o seguinte ao application.properties: spring.jackson.serialization.write-date-as-timestamps = false
Joost Lambregts
46

No aplicativo da web Spring Boot, com Jackson e JSR 310 versão "2.8.5"

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

Os @JsonFormattrabalhos:

import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;
Tsolak Barseghyan
fonte
2
Isso funciona para desserialização? ou apenas serialização? Não tendo sucesso com desserialização
rewolf
7
Eu tinha que declarar explicitamente o desserializador@JsonDeserialize(using= LocalDateDeserializer.class)
rewolf
1
de longe o mais simples!
Antonio
@JsonFormatapenas para alterar o formato dos dados de saída. stackoverflow.com/a/53251526/816759 funciona perfeito com @JsonFormat, @JsonDeserialize,@JsonSerialize
Baha
No Spring Boot, depois de adicionar a dependência do JSR310, tudo o que você precisa fazer é adicionar spring.jackson.serialization.write-dates-as-timestamps=falseao seu application.propertiese ele o formata yyyy-MM-ddautomaticamente. Não é necessário@JsonFormat
esfandia 29/03
31

A solução mais simples (que também suporta desserialização e serialização) é

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;

Enquanto estiver usando as seguintes dependências no seu projeto.

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"

Nenhuma implementação adicional de um ContextResolver, Serializer ou Deserializer é necessária.

Paul Wasilewski
fonte
Brilhante, de longe o mais fácil. Para quem tem muitas dependências, tive que atualizar algumas outras bibliotecas que incorporavam anotações de jackson.
brt 28/01/19
Esta resposta é a mais próxima que eu tenho para resolver o meu problema. A serialização está funcionando, mas a desserialização está falhando por causa do padrão que eu usei com o @JsonFormat que eu acho (@JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-aaaa_HH: mm: SS").
fsakiyama
Se você teve uma desserialização com falha, provavelmente o seu ObjectMappernão foi JavaTimeModuleregistrado. Se sua instância do ObjectMapper for fornecida a partir da estrutura spring / MessageConverter. Eles fizeram alguma mágica para conectá-los. Em outro caso, deve registerModulehabilitar LocalDateDeserializerpor padrão para todos os "LocalDate" no POJO
Dennis C
19

Desde que o LocalDateSerializertransforma em "[ano, mês, dia]" (uma matriz json) em vez de "ano-mês-dia" (uma sequência json) por padrão, e como eu não quero exigir nenhuma ObjectMapperconfiguração especial (você pode fazer LocalDateSerializergerar seqüências de caracteres se você desativar, SerializationFeature.WRITE_DATES_AS_TIMESTAMPSmas isso requer configuração adicional para o seu ObjectMapper), eu uso o seguinte:

importações:

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;

E agora eu posso apenas new ObjectMapper()ler e escrever meus objetos sem nenhuma configuração especial.

Homem sombra
fonte
3
Uma coisa que eu gostaria de adicionar é passar a data, pois em "2018-12-07"vez disso "2018-12-7", você receberá um erro.
Kid101
1
Correto, ele funciona com o formato yyyy-MM-dd(2 dígitos mês e dia), não com o formato yyyy-M-d(1 dígito mês ou dia).
Shadow Man
8

Apenas uma atualização da resposta de Christopher.

Desde a versão 2.6.0

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

Use o JavaTimeModule em vez de JSR310Module (descontinuado).

@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 acordo com a documentação , o novo JavaTimeModule usa as mesmas configurações padrão para a serialização que NÃO usa IDs de fuso horário e, em vez disso, usa apenas compensações de fuso horário compatíveis com ISO-8601.

O comportamento pode ser alterado usando SerializationFeature.WRITE_DATES_WITH_ZONE_ID

bdzzaid
fonte
Isso me ajudou. No meu caso, eu precisava adicionar a MAPPER.registerModule(new JavaTimeModule());linha. Deixe-me formatar objetos LocalDate como formato "2020-02-20". Eu não precisava da MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);linha, pelo que estava procurando
Cuga
6

A anotação a seguir funcionou bem para mim.

Não são necessárias dependências extras.

    @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
fonte
5
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createdDate;
slisnychyi
fonte
4

https://stackoverflow.com/a/53251526/1282532 é a maneira mais simples de serializar / desserializar a propriedade. Eu tenho duas preocupações com essa abordagem - até algum ponto da violação do princípio DRY e alto acoplamento entre 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;
}

Caso você tenha um POJO com vários campos LocalDate, é melhor configurar o mapeador em vez do POJO. Pode ser tão simples quanto https://stackoverflow.com/a/35062824/1282532 se você estiver usando valores ISO-8601 ("2019-01-31")

Caso você precise manipular o formato personalizado, o código será assim:

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

A lógica é escrita apenas uma vez, pode ser reutilizada para vários POJO

Pavel
fonte
3

Mais simples e mais curto até agora:

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

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

nenhuma dependência necessária com a inicialização Spring> = 2.2+

Anil Bhaskar
fonte
1

A partir de 2020 e Jackson 2.10.1, não há necessidade de nenhum código especial, é apenas uma questão de dizer ao Jackson o que você deseja:

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

Isso já foi mencionado nesta resposta , estou adicionando um teste de unidade para verificar a funcionalidade:

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

O TestBean usa o Lombok para gerar o padrão para o bean.

Bogdan Calmac
fonte
0

Na classe de configuração, defina LocalDateSerializer e LocalDateDeserializer e registre-as no ObjectMapper via JavaTimeModule, como abaixo:

@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
fonte
0

Se sua solicitação contiver um objeto como este:

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

Então você pode 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) }
    }
}

Acima do campo:

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

O código está no Kotlin, mas isso também funcionaria para Java, é claro.

mowwwalker
fonte