Formato de data Mapeando para JSON Jackson

154

Eu tenho um formato de data vindo da API como este:

"start_time": "2015-10-1 3:00 PM GMT+1:00"

Qual é o carimbo de data / hora AAAA-DD-MM HH: MM am / pm GMT. Estou mapeando esse valor para uma variável Date no POJO. Obviamente, está mostrando erro de conversão.

Eu gostaria de saber 2 coisas:

  1. Qual é a formatação que preciso usar para realizar a conversão com Jackson? Data é um bom tipo de campo para isso?
  2. Em geral, existe uma maneira de processar as variáveis ​​antes que elas sejam mapeadas para membros do objeto por Jackson? Algo como, alterar o formato, cálculos, etc.
Kevin Rave
fonte
Este é realmente bom exemplo, lugar anotação no campo da classe: java.dzone.com/articles/how-serialize-javautildate
digz6666
Agora eles têm uma página wiki para o tratamento de datas: wiki.fasterxml.com/JacksonFAQDateHandling
Sutra
Hoje em dia você não deve mais usar a Dateclasse. O java.time, a moderna API de data e hora do Java, a substituiu há quase 5 anos. Use-o e o FasterXML / jackson-modules-java8 .
Ole VV

Respostas:

124

Qual é a formatação que preciso usar para realizar a conversão com Jackson? Data é um bom tipo de campo para isso?

Dateé um tipo de campo fino para isso. Você pode tornar o JSON analisável com bastante facilidade usando ObjectMapper.setDateFormat:

DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
myObjectMapper.setDateFormat(df);

Em geral, existe uma maneira de processar as variáveis ​​antes que elas sejam mapeadas para membros do objeto por Jackson? Algo como, alterar o formato, cálculos, etc.

Sim. Você tem algumas opções, incluindo a implementação de um costume JsonDeserializer, por exemplo, extensão JsonDeserializer<Date>. Este é um bom começo.

pb2q
fonte
2
O formato de 12 horas é melhor se o formato também incluir a designação AM / PM: DateFormat df = new SimpleDateFormat ("aaaa-MM-dd hh: mm a z");
precisa
Tentei todas essas soluções, mas não foi capaz de armazenar uma variável Date do meu POJO em um valor de chave Map, também como Date. Quero que isso instancie um BasicDbObject (API do MongoDB) a partir do mapa e, consequentemente, armazene a variável em uma coleção de banco de dados do MongoDB como Data (não como Long ou String). Isso é possível? Obrigado
RedEagle
1
É tão fácil usar o Java 8 LocalDateTimeou em ZonedDateTimevez de Date? Como Dateestá basicamente obsoleto (ou pelo menos muitos de seus métodos), eu gostaria de usar essas alternativas.
houcros
Os javadocs para setSateFormat () dizem que esta chamada faz com que o ObjectMapper não seja mais Thread seguro. Criei as classes JsonSerializer e JsonDeserializer.
MiguelMunoz 14/02
Como a pergunta não menciona java.util.Dateexplicitamente, quero salientar que isso não funciona. java.sql.Date.Veja também minha resposta abaixo.
brass monkey
329

Desde o Jackson v2.0, você pode usar a anotação @JsonFormat diretamente nos membros do Object;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;
Olivier Lecrivain
fonte
61
Se você deseja incluir o fuso horário:@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
realPK
Oi, Qual jar tem essa anotação. Estou usando a versão jackson-mapper-asl 1.9.10. Eu não estou recebendo-lo
krishna Ram
1
@Ramki: jackson-annotations> = 2.0
Olivier Lecrivain
3
Essa anotação funciona perfeitamente na fase de serialização, mas durante a desserialização, as informações de fuso horário e código de idioma não são usadas. Tentei o fuso horário = "CET" e o fuso horário "Europa / Budapeste" com o código do idioma = "hu", mas nenhum funciona e causa uma conversa estranha no Calendário. Somente a serialização personalizada com desserialização funcionou para mim, manipulando o fuso horário, conforme necessário. Aqui está o tutorial perfeito como você precisa usar baeldung.com/jackson-serialize-dates
Miklos Krivan
1
Este é um projeto jar separado chamado Jackson Annotations . Exemplo de entrada de pom
bub
52

Obviamente, existe uma maneira automatizada chamada serialização e desserialização e você pode defini-la com anotações específicas ( @JsonSerialize , @JsonDeserialize ), conforme mencionado pelo pb2q.

Você pode usar java.util.Date e java.util.Calendar ... e provavelmente o JodaTime também.

As anotações @JsonFormat não funcionaram para mim como eu queria ( ajustou o fuso horário para um valor diferente) durante a desserialização (a serialização funcionou perfeitamente):

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "CET")

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Europe/Budapest")

Você precisa usar o serializador personalizado e o desserializador personalizado em vez da anotação @JsonFormat, se desejar um resultado previsto. Encontrei um bom tutorial e solução aqui http://www.baeldung.com/jackson-serialize-dates

Existem exemplos para os campos Data , mas eu precisava para os campos Calendário , então aqui está minha implementação :

A classe do serializador :

public class CustomCalendarSerializer extends JsonSerializer<Calendar> {

    public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    public static final Locale LOCALE_HUNGARIAN = new Locale("hu", "HU");
    public static final TimeZone LOCAL_TIME_ZONE = TimeZone.getTimeZone("Europe/Budapest");

    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider arg2)
            throws IOException, JsonProcessingException {
        if (value == null) {
            gen.writeNull();
        } else {
            gen.writeString(FORMATTER.format(value.getTime()));
        }
    }
}

A classe desserializador :

public class CustomCalendarDeserializer extends JsonDeserializer<Calendar> {

    @Override
    public Calendar deserialize(JsonParser jsonparser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        String dateAsString = jsonparser.getText();
        try {
            Date date = CustomCalendarSerializer.FORMATTER.parse(dateAsString);
            Calendar calendar = Calendar.getInstance(
                CustomCalendarSerializer.LOCAL_TIME_ZONE, 
                CustomCalendarSerializer.LOCALE_HUNGARIAN
            );
            calendar.setTime(date);
            return calendar;
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

e o uso das classes acima:

public class CalendarEntry {

    @JsonSerialize(using = CustomCalendarSerializer.class)
    @JsonDeserialize(using = CustomCalendarDeserializer.class)
    private Calendar calendar;

    // ... additional things ...
}

Usando essa implementação, a execução do processo de serialização e desserialização resulta consecutivamente no valor de origem.

Somente usando a anotação @JsonFormat, a desserialização dá um resultado diferente , acho que, devido à configuração padrão do fuso horário interno da biblioteca, o que você não pode alterar com os parâmetros de anotação (essa também foi minha experiência com as versões 2.5.3 e 2.6.3 da biblioteca Jackson).

Miklos Krivan
fonte
4
Ontem recebi voto negativo pela minha resposta. Eu trabalhei muito nesse tópico, então não entendo. Posso receber algum feedback para aprender com isso? Eu apreciaria algumas notas em caso de voto negativo. Dessa forma, podemos aprender mais um com o outro.
precisa saber é o seguinte
Ótima resposta, obrigado, realmente me ajudou! Sugestão secundária - considere colocar CustomCalendarSerializer e CustomCalendarDeserializer como classes estáticas em uma classe pai anexa. Eu acho que isso faria o código um pouco mais agradável :)
Stuart
@Stuart - você deve simplesmente oferecer essa reorganização do código como outra resposta ou propor uma edição. Miklos pode não ter tempo livre para fazê-lo.
Ocodo 01/04
@MiklosKrivan Eu diminuí sua votação por várias razões. Você deve saber que o SimpleDateFormat não é seguro para threads e, francamente, você deve usar bibliotecas alternativas para formatação de datas (Joda, FastDateFormat, Commons-lang, etc). O outro motivo é a configuração do fuso horário e até da localidade. É muito mais preferível usar o GMT no ambiente de serialização e deixar seu cliente dizer em qual fuso horário está ou até anexar o tz preferido como uma sequência separada. Defina seu servidor como GMT, também conhecido como UTC. Jackson incorporou o formato ISO.
Adam Gent
1
Thx @AdamGent pelo seu feedback. Compreendo e aceito suas sugestões. Mas nesse caso em particular, eu só queria focar que a anotação JsonFormat com informações de localidade não está funcionando da maneira que esperávamos. E como pode ser resolvido.
Miklos Krivan
4

Apenas um exemplo completo para o aplicativo de inicialização com RFC3339formato de data e hora

package bj.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

import java.text.SimpleDateFormat;

/**
 * Created by [email protected] at 2018/5/4 10:22
 */
@SpringBootApplication
public class BarApp implements ApplicationListener<ApplicationReadyEvent> {

    public static void main(String[] args) {
        SpringApplication.run(BarApp.class, args);
    }

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
    }
}
BaiJiFeiLong
fonte
4

Para adicionar caracteres como T e Z na sua data

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
private Date currentTime;

resultado

{
    "currentTime": "2019-12-11T11:40:49Z"
}
Odwori
fonte
3

Trabalhando para mim. SpringBoot.

 import com.alibaba.fastjson.annotation.JSONField;

 @JSONField(format = "yyyy-MM-dd HH:mm:ss")  
 private Date createTime;

resultado:

{ 
   "createTime": "2019-06-14 13:07:21"
}
anson
fonte
2

Com base na resposta muito útil de @ miklov-kriven, espero que esses dois pontos adicionais de consideração sejam úteis para alguém:

(1) Acho uma boa ideia incluir serializador e desserializador como classes internas estáticas na mesma classe. NB, usando ThreadLocal para segurança de thread de SimpleDateFormat.

public class DateConverter {

    private static final ThreadLocal<SimpleDateFormat> sdf = 
        ThreadLocal.<SimpleDateFormat>withInitial(
                () -> {return new SimpleDateFormat("yyyy-MM-dd HH:mm a z");});

    public static class Serialize extends JsonSerializer<Date> {
        @Override
        public void serialize(Date value, JsonGenerator jgen SerializerProvider provider) throws Exception {
            if (value == null) {
                jgen.writeNull();
            }
            else {
                jgen.writeString(sdf.get().format(value));
            }
        }
    }

    public static class Deserialize extends JsonDeserializer<Date> {
        @Overrride
        public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws Exception {
            String dateAsString = jp.getText();
            try {
                if (Strings.isNullOrEmpty(dateAsString)) {
                    return null;
                }
                else {
                    return new Date(sdf.get().parse(dateAsString).getTime());
                }
            }
            catch (ParseException pe) {
                throw new RuntimeException(pe);
            }
        }
    }
}

(2) Como alternativa ao uso das anotações @JsonSerialize e @JsonDeserialize em cada membro da classe, você também pode substituir a serialização padrão de Jackson aplicando a serialização personalizada no nível do aplicativo, ou seja, todos os membros da classe do tipo Date serão serializados por Jackson usando essa serialização customizada sem anotação explícita em cada campo. Se você estiver usando o Spring Boot, por exemplo, uma maneira de fazer isso seria da seguinte maneira:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Module customModule() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(Date.class, new DateConverter.Serialize());
        module.addDeserializer(Date.class, new Dateconverter.Deserialize());
        return module;
    }
}
Stuart
fonte
Eu votei contra você (odeio votar, mas não quero que as pessoas usem sua resposta). SimpleDateFormat não é seguro para threads. É 2016 (você respondeu em 2016). Você não deve usar o SimpleDateFormat quando houver várias opções mais rápidas e seguras para threads. Existe até um uso indevido exato do que você está propondo Q / A aqui: stackoverflow.com/questions/25680728/…
Adam Gent
2
@AdamGent, obrigado por apontar isso. Nesse contexto, usando Jackson, a classe ObjectMapper é segura para threads, portanto não importa. No entanto, entendo que o código pode ser copiado e usado em um contexto seguro sem thread. Portanto, editei minha resposta para tornar seguro o acesso ao thread SimpleDateFormat. Também reconheço que existem alternativas, principalmente o pacote java.time.
Stuart
2

Se alguém tiver problemas com o uso de um formato de data personalizado para java.sql.Date, esta é a solução mais simples:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(java.sql.Date.class, new DateSerializer());
mapper.registerModule(module);

(Essa resposta SO me poupou muitos problemas: https://stackoverflow.com/a/35212795/3149048 )

Jackson usa o SqlDateSerializer por padrão para java.sql.Date, mas atualmente, esse serializador não leva em conta o formato da data, consulte este problema: https://github.com/FasterXML/jackson-databind/issues/1407 . A solução alternativa é registrar um serializador diferente para java.sql.Date, conforme mostrado no exemplo de código.

Stephanie
fonte
1

Quero salientar que definir um exemplo SimpleDateFormatdescrito na outra resposta só funciona para o java.util.Dateque eu suponho que se refira à pergunta. Mas para java.sql.Dateo formatador não funciona. No meu caso, não era muito óbvio por que o formatador não funcionou, porque no modelo que deveria ser serializado o campo era de fato um, java.utl.Datemas o objeto real acabou sendo um java.sql.Date. Isso é possível porque

public class java.sql extends java.util.Date

Então isso é realmente válido

java.util.Date date = new java.sql.Date(1542381115815L);

Portanto, se você está se perguntando por que o campo Data não está formatado corretamente, verifique se o objeto é realmente um java.util.Date.

Aqui também é mencionado por que o manuseio java.sql.Datenão será adicionado.

Isso seria quebra de mudança, e eu não acho que isso se justifique. Se estivéssemos começando do zero, eu concordaria com a mudança, mas como as coisas não são muito.

macaco de bronze
fonte
1
Obrigado por apontar uma consequência desse mau design das duas Dateclasses diferentes . Hoje em dia, não se deve nem um deles SimpleDateFormat. O java.time, a moderna API de data e hora do Java, os substituiu há quase 5 anos. Use-o e o FasterXML / jackson-modules-java8 .
Ole VV