Formato de fecha Mapping to JSON Jackson

Tengo un formato de fecha procedente de una API como esta:

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

Que es YYYY-DD-MM HH: MM a.m./pm GMT fecha y hora. Estoy mapeando este valor a una variable de fecha en POJO. Obviamente, muestra el error de conversión.

Me gustaría saber 2 cosas:

  1. ¿Cuál es el formato que necesito usar para llevar a cabo la conversión con Jackson? ¿Es la fecha un buen tipo de campo para esto?
  2. En general, ¿hay alguna forma de procesar las variables antes de que Jackson las asigne a los miembros del Objeto? Algo así como cambiar el formato, los cálculos, etc.

¿Cuál es el formato que necesito usar para llevar a cabo la conversión con Jackson? ¿Es la fecha un buen tipo de campo para esto?

Date es un buen tipo de campo para esto. Puede hacer que el JSON se pueda analizar fácilmente usando ObjectMapper.setDateFormat :

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

En general, ¿hay alguna forma de procesar las variables antes de que Jackson las asigne a los miembros del Objeto? Algo así como cambiar el formato, los cálculos, etc.

Sí. Tiene algunas opciones, incluida la implementación de un JsonDeserializer personalizado, por ejemplo, ampliando JsonDeserializer . Este es un buen comienzo.

Desde Jackson v2.0, puede usar la anotación @JsonFormat directamente en los miembros del objeto;

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

Por supuesto, existe una forma automatizada llamada serialización y deserialización y puede definirla con anotaciones específicas ( @JsonSerialize , @JsonDeserialize ) como también lo menciona pb2q.

Puede usar tanto java.util.Date como java.util.Calendar … y probablemente JodaTime también.

Las anotaciones de @JsonFormat no me funcionaron como quería (se ajustó la zona horaria a un valor diferente) durante la deserialización (la serialización funcionó perfectamente):

 @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") 

Necesita utilizar un serializador personalizado y un deserializador personalizado en lugar de la anotación @JsonFormat si desea un resultado pronosticado. He encontrado un buen tutorial y solución aquí http://www.baeldung.com/jackson-serialize-dates

Hay ejemplos para los campos de Fecha , pero los necesitaba para los campos del Calendario , así que aquí está mi implementación :

La clase de 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())); } } } 

La clase de deserializador :

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

y el uso de las clases anteriores:

 public class CalendarEntry { @JsonSerialize(using = CustomCalendarSerializer.class) @JsonDeserialize(using = CustomCalendarDeserializer.class) private Calendar calendar; // ... additional things ... } 

Usando esta implementación, la ejecución del proceso de serialización y deserialización da como resultado el valor de origen.

Solo utilizando la anotación @JsonFormat, la deserialización arroja un resultado diferente. Creo que debido a la configuración predeterminada de la zona horaria interna de la biblioteca no se puede cambiar con los parámetros de anotación (esa fue también mi experiencia con la versión 2.5.3 y 2.6.3 de la biblioteca Jackson).

Sobre la base de la respuesta muy útil de @miklov-kriven, espero que estos dos puntos adicionales de consideración resulten útiles para alguien:

(1) Me parece una buena idea incluir el serializador y el deserializador como clases internas estáticas en la misma clase. NB, utilizando ThreadLocal para seguridad de hilos 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 al uso de las anotaciones @JsonSerialize y @JsonDeserialize en cada miembro de clase individual, también podría considerar anular la serialización predeterminada de Jackson aplicando la serialización personalizada a nivel de aplicación, es decir, todos los miembros de la clase del tipo Date serán serializados por Jackson utilizando esta serialización personalizada sin anotación explícita en cada campo. Si está utilizando Spring Boot, por ejemplo, una forma de hacerlo sería la siguiente:

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

Si alguien tiene problemas con el uso de un formato de fecha personalizado para java.sql.Date, esta es la solución más simple:

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

(Esta respuesta SO me ahorró muchos problemas: https://stackoverflow.com/a/35212795/3149048 )

Jackson usa el SqlDateSerializer de forma predeterminada para java.sql.Date, pero actualmente, este serializador no tiene en cuenta el formato de fecha; consulte este tema: https://github.com/FasterXML/jackson-databind/issues/1407 . La solución consiste en registrar un serializador diferente para java.sql.Date como se muestra en el ejemplo del código.

Solo un ejemplo completo para la aplicación de arranque de spring con formato de fecha RFC3339 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")); } }