Mapeamento de formato de data para JSON Jackson

Eu tenho um formato de data vindo da API assim:

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

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

Eu gostaria de saber duas 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 variables ​​antes de serem mapeadas para os membros do object por Jackson? Algo como mudar o formato, cálculos, etc.

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 bom para isso. Você pode tornar o JSON parse-able facilmente usando ObjectMapper.setDateFormat :

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

Em geral, existe uma maneira de processar as variables ​​antes de serem mapeadas para os membros do object por Jackson? Algo como mudar o formato, cálculos, etc.

Sim. Você tem algumas opções, incluindo a implementação de um JsonDeserializer personalizado, por exemplo, ampliando o JsonDeserializer . Este é um bom começo.

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

 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm az") private Date date; 

É claro que existe uma maneira automatizada chamada serialização e desserialização e você pode defini-la com annotations específicas ( @JsonSerialize , @JsonDeserialize ) como mencionado pelo pb2q também.

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

As annotations do @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 customizado em vez da anotação @JsonFormat se desejar o resultado previsto. Eu encontrei muito bom tutorial e solução aqui http://www.baeldung.com/jackson-serialize-dates

Existem exemplos para campos de data , mas eu precisava dos campos do calendar , então aqui está minha implementação :

A class do serializador :

 public class CustomCalendarSerializer extends JsonSerializer { 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 class do desserializador :

 public class CustomCalendarDeserializer extends JsonDeserializer { @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 classs 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 da origem.

Apenas usando a anotação @JsonFormat a desserialização dá um resultado diferente, eu acho que por causa da configuração padrão do fuso horário interno da biblioteca o que você não pode mudar com os parâmetros de anotação (essa também foi minha experiência com a biblioteca 2.5.3 e 2.6.3).

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

(1) Acho uma boa idéia include serializador e desserializador como classs internas estáticas na mesma class. NB, usando ThreadLocal para segurança de segmento de SimpleDateFormat.

 public class DateConverter { private static final ThreadLocal sdf = ThreadLocal.withInitial( () -> {return new SimpleDateFormat("yyyy-MM-dd HH:mm az");}); public static class Serialize extends JsonSerializer { @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 { @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 de annotations @JsonSerialize e @JsonDeserialize em cada membro da class individual, você também pode considerar a substituição da serialização padrão do Jackson aplicando a serialização customizada no nível do aplicativo, ou seja, todos os membros da class do tipo Data serão serializados por Jackson usando essa serialização personalizada sem anotação explícita em cada campo. Se você estiver usando o Spring Boot, por exemplo, uma maneira de fazer isso seria a seguinte:

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

Se alguém tiver problemas com o uso de um formato de data customizado 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, este serializador não leva em conta o formato date, veja este problema: https://github.com/FasterXML/jackson-databind/issues/1407 . A solução é registrar um serializador diferente para java.sql.Date, conforme mostrado no exemplo de código.

Apenas um exemplo completo para o aplicativo de boot de mola com formato de data e hora RFC3339

 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 BaiJiFeiLong@gmail.com at 2018/5/4 10:22 */ @SpringBootApplication public class BarApp implements ApplicationListener { 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")); } }