serialize / desserialize java 8 java.time com o mapeador JSON de Jackson

221

Como uso o mapeador Jackson JSON com o Java 8 LocalDateTime?

org.codehaus.jackson.map.JsonMappingException: Não é possível instanciar o valor do tipo [tipo simples, classe java.time.LocalDateTime] de JSON String; nenhum construtor de cadeia única / método de fábrica (através da cadeia de referência: MyDTO ["field1"] -> SubDTO ["date"])

Alexander Taylor
fonte
4
tem certeza de que deseja mapear um LocalDateTime para JSon? Até onde eu sei, o JSon não tem um formato para datas, embora o JavaScript use a ISO-8601. O problema é que LocalDateTime não possui um fuso horário ... portanto, se você usar o JSON como meio para enviar informações de data / hora, poderá ter problemas se o cliente interpretar a falta de fuso horário como o UTC padrão (ou o seu próprio fuso horário). Se é isso que você quer fazer, é claro que está bem. Mas apenas verificar se você considerou usando ZonedDateTime vez
arcuri82

Respostas:

276

Não há necessidade de usar serializadores / desserializadores personalizados aqui. Use o módulo datetime de jackson-modules-java8 :

Módulo de tipo de dados para fazer Jackson reconhecer os tipos de dados da API Java 8 Date & Time API (JSR-310).

Este módulo adiciona suporte para várias classes:

  • Duração
  • Instante
  • LocalDateTime
  • LocalDate
  • Horário local
  • Dia do mês
  • OffsetDateTime
  • OffsetTime
  • Período
  • Ano
  • Ano mês
  • ZonedDateTime
  • ZoneId
  • ZoneOffset
Matt Ball
fonte
59
Ai sim! Eu pensei que isso não estava funcionando porque eu não sabia que era preciso fazer uma dessas customizações no objeto mapeador: registerModule(new JSR310Module())ou findAndRegisterModules(). Veja github.com/FasterXML/jackson-datatype-jsr310 e aqui é como personalizar o seu mapeador de objeto se você usar o framework Spring: stackoverflow.com/questions/7854030/...
Alexander Taylor
10
Não funciona com o mais simples eu tentei com,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 você usa a estrutura recente do Spring, não há mais necessidade de se personalizar, já que os módulos Jackson conhecidos como este agora são registrados automaticamente se forem detectados no caminho de classe: spring.io/blog/2014/12/02/…
vorburger 30/10/2015
36
JSR310Module é um pouco já ultrapassada, em vez usar JavaTimeModule
Jacob Eckel
17
@MattBall eu sugiro acrescentando que, além de usar jackson-datatype-jsr310, você precisa registrar o JavaTimeModule com o mapeador objeto: objectMapper.registerModule(new JavaTimeModule());. Tanto na serialização quanto na desserialização.
GrandAdmiral
76

Atualização: Deixar esta resposta por razões históricas, mas não a recomendo. Por favor, veja a resposta aceita acima.

Diga a Jackson para mapear usando suas classes de [de] serialização personalizadas:

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

forneça classes 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());
    }
}

fato aleatório: se eu aninhar acima das classes e não as tornar estáticas, a mensagem de erro é estranha: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported

Alexander Taylor
fonte
até o momento em que este artigo foi escrito, a única vantagem aqui sobre outras respostas não depende do FasterXML, seja o que for.
Alexander Taylor
4
FasterXML é Jackson. fasterxml.com github.com/fasterxml/jackson
Matt Bola
4
Ah eu vejo. tantas entradas antigas de pom no projeto em que estou trabalhando. portanto, não há mais org.codehaus, é tudo com.fasterxml agora. obrigado!
Alexander Taylor
2
Também não consegui usar o embutido porque precisava passar o formatador ISO_DATE_TIME. Não tenho certeza se é possível fazer isso ao usar o JSR310Module.
Isaac.hazan
Usando este com a Primavera Eu tive que criar um bean de ambos LocalDateTimeSerializereLocalDateTimeDeserializer
BenR
53

Se você estiver usando a classe ObjectMapper de quickxml, por padrão, o ObjectMapper não entenderá a classe LocalDateTime, portanto, será necessário adicionar outra dependência em seu gradle / maven:

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

Agora você precisa registrar o suporte ao tipo de dados oferecido por esta biblioteca no objeto objectmapper, isso pode ser feito da seguinte maneira:

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

Agora, em seu jsonString, você pode facilmente colocar seu campo java.LocalDateTime da seguinte maneira:

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

Ao fazer tudo isso, seu arquivo Json para conversão de objeto Java funcionará bem. Você pode ler o arquivo da seguinte maneira:

objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
            });
greperror
fonte
4
findAndRegisterModules()foi uma peça fundamental que faltava para mim quando construção de umObjectMapper
Xaero Degreaz
2
E o Instant?
powder366
1
Eu também adicionei esta linha: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Janet
este é o código completo necessário: ObjectMapper objectMapper = new ObjectMapper (); objectMapper.findAndRegisterModules (); objectMapper.configure (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Khush
42

Essa dependência inteligente resolverá o seu problema:

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

Uma coisa que lutei é que, para o fuso horário do ZonedDateTime, seja alterado para GMT durante a desserialização. Acabou que, por padrão, jackson o substitui por um do contexto. Para manter a zona, é necessário desativar esse 'recurso'

Jackson2ObjectMapperBuilder.json()
    .featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
eu pego
fonte
5
Muito obrigado pela dica DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE.
Andrei Urvantcev 28/07
5
Além de desativar DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, também tive que desativar SerializationFeature.WRITE_DATES_AS_TIMESTAMPSpara que tudo começasse a funcionar como deveria.
Matt Watson
16

Eu tive um problema semelhante ao usar a inicialização do Spring . Com o Spring boot 1.5.1.RELEASE, tudo o que eu precisava fazer era adicionar dependência:

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

Se você estiver usando Jersey, precisará adicionar a dependência do Maven (jackson-datatype-jsr310) conforme as outras sugestões e registrar sua instância do mapeador de objetos da seguinte maneira:

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

Ao registrar Jackson em seus recursos, você precisa adicionar este mapeador da seguinte forma:

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

Se você não puder usar jackson-modules-java8por qualquer motivo, poderá (des) serializar o campo instantâneo 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);
    }
}

Exemplo:

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

rendimentos

{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z
Udo
fonte
Este é um método muito limpo e permite que os usuários de sua classe usem da maneira que quiserem.
Ashhar Hasan
3

Eu uso este formato de hora: "{birthDate": "2018-05-24T13:56:13Z}"para desserializar de json para java.time.Instant (veja a captura de tela)

insira a descrição da imagem aqui

Taras Melnyk
fonte
3

Este é apenas um exemplo de como usá-lo em um teste de unidade que eu hackeei para depurar esse problema. Os principais ingredientes são

  • mapper.registerModule(new JavaTimeModule());
  • dependência dependente 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
fonte
2

Se você estiver usando a inicialização do Spring e tiver esse problema com o OffsetDateTime, precisará usar o registerModules como respondido acima por @greperror (respondida em 28 de maio de 16 às 13:04), mas observe que há uma diferença. A dependência mencionada não precisa ser adicionada, pois acho que a bota de mola já a possui. Eu estava tendo esse problema com a inicialização do Spring e funcionou para mim sem adicionar essa dependência.

VC2019
fonte
1

Você pode definir isso no seu application.ymlarquivo para resolver o Instant time, que é a API da data em java8:

spring.jackson.serialization.write-dates-as-timestamps=false
Janki
fonte
1

Para quem usa o Spring Boot 2.x

Não há necessidade de executar nenhuma das ações acima - o Java 8 LocalDateTime é serializado / desserializado imediatamente. Eu tive que fazer tudo o que precede no 1.x, mas com o Boot 2.x, ele funciona perfeitamente.

Consulte esta referência também no formato JSON Java 8 LocalDateTime no Spring Boot

HopeKing
fonte
1

Se alguém com problemas durante o uso SpringBootaqui é como eu corrigi o problema sem adicionar nova dependência.

Em Spring 2.1.3Jackson espera que a string de data 2019-05-21T07:37:11.000neste yyyy-MM-dd HH:mm:ss.SSSformato seja desserializada em LocalDateTime. Verifique se a sequência de datas separa a data e a hora com Tnão space. segundos ( ss) e milissegundos ( SSS) podem ser omitidos.

@JsonProperty("last_charge_date")
public LocalDateTime lastChargeDate;
Muhammad Usman
fonte
0

Se você considerar o uso do fastjson, poderá resolver seu problema, observe a versão

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

Se você está tendo esse problema devido ao GraphQL Java Tools e tentando organizar um Java Instant partir de uma sequência de datas, precisa configurar o SchemaParser para usar um ObjectMapper com determinadas configurações:

Na sua classe GraphQLSchemaBuilder, injete o ObjectMapper e adicione estes módulos:

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

e adicione-o às opções:

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

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

Janac Meena
fonte
0

Se você estiver usando o Jackson Serializer , aqui está uma maneira de usar os módulos de 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;
    }
}
edubriguenti
fonte