Jackson 2.x Formatear fechas (Date, LocalDate, etc.)

En el tutorial anterior aprendimos como usar la clase ObjectMapper de la librería Jackson 2.x para leer y escribir archivos o cadenas de texto en formato JSON, en esta publicación nos centraremos en el trabajo con las clases usadas por Java para representar fechas, como: Date, LocalDate, DateTime, etc., ya sea usando la API Java 8 o Joda Time.

Primero veamos qué pasa cuando deseamos convertir un objeto java.util.Date a formato JSON, el código es el siguiente:

Date fecha = new Date();
ObjectMapper mapper = new ObjectMapper();

String out = mapper.writeValueAsString(fecha);
System.out.println("Hoy es: " + out);

Este código produce la salida:

Hoy es: 1493568561678

Este número que vemos representa la fecha en milisegundos transcurridos a partir de un punto de referencia, guardar una fecha de esta manera no es un error, el ObjectMapper puede convertir este número a la fecha adecuada, pero que pasa si deseamos guardar la fecha en un formato más legible, la primera opción es la siguiente:

Date fecha = new Date();

ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        
String out = mapper.writeValueAsString(fecha);
System.out.println("Hoy es: " + out);

La salida al deshabilitar la opción WRITE_DATES_AS_TIMESTAMPS se ve de esta manera:

Hoy es: "2017-04-30T16:18:02.341+0000"

Como vemos la cadena JSON es más legible, aun así, contiene información que nos deseamos o está en un formato que tal vez no sea de nuestro gusto, si ese es el caso podemos cambiar el formato en que Jackson 2.x serializa las fechas, esto se puede hacer de dos maneras, usando una clase apropiada para formatear o con anotaciones.

1. Formatear fechas usando la clase SimpleDateFormat

Usando la clase SimpleDateFormat podemos indicar el patrón que se utilizara para formatear la fecha, para indicarle al ObjectMapper que debe usar esta clase para formatear los objetos Date usaremos el método mapper.setDateFormat(sdf), ejemplo:

Date fecha = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy hh:mm");
        
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(sdf);

String out = mapper.writeValueAsString(fecha);
System.out.println("Hoy es: " + out);

Nuestro formato está definido por la cadena "dd/MM/yy hh:mm" y genera las siguiente salida:

Hoy es: "01/05/17 10:09"

2. Formatear fechas con la anotación @JsonFormat

Para el código de ejemplo de uso de la anotación @JsonFormat crearemos las siguiente clase que nos ayudará a demostrar su uso.

public class Example {

    private String nombre;

    @JsonFormat(pattern = "dd-MM-yyyy hh:mm:ss")
    private Date fecha;

    public Example(String nombre, Date fecha) {
        this.nombre = nombre;
        this.fecha = fecha;
    }
}

Al igual que el anterior el patrón está definido por la cadena "dd-MM-yyyy hh:mm:ss", no es necesario configurar algo, Jackson detectará la anotación y formateara la salida de manera correspondiente.

Example ex = new Example("carmelo", Date.from(Instant.now()));

ObjectMapper mapper = new ObjectMapper();

String out = mapper
        .writerWithDefaultPrettyPrinter()
        .writeValueAsString(ex);

System.out.println("Example object: " + out);

La cadena en formato JSON es la siguiente:

Example object: {
  "nombre" : "carmelo",
  "fecha" : "01-05-2017 04:17:33"
}

Usar Date-Time API de Java 8 en Jackson 2.x

Para utilizar la Date-Time API introducida con Java 8 debemos primero, agregar la dependencia jackson-datatype-jsr310 a nuestro proyecto, luego debemos registrar el módulo correspondiente, llamado: JSR310Module, vemos el ejemplo.

El archivo POM requiere las siguientes dependencias:

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.8.8</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
        <version>2.4.0</version>
    </dependency>
</dependencies>

Registrar el módulo JSR310Module:

LocalDate fecha = LocalDate.of(2010, Month.MARCH, 20);

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JSR310Module());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

String out = mapper.writeValueAsString(fecha);
System.out.println("java.time.LocalDate: " + out);

Lo siguiente que haremos será crear un serializer personalizado para la clase LocalDate, este nos permitirá definir como se escribe la cadena JSON a partir de un objeto LocalDate, para crear esta clase debemos extender la clase base StdSerializer<T>, de este modo:

public class LocalDateSerializer extends StdSerializer<LocalDate> {
    
    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd::MM::yy");

    public LocalDateSerializer() {
        this(LocalDate.class);
    }

    public LocalDateSerializer(Class<LocalDate> date) {
        super(date);
    }

    @Override
    public void serialize(LocalDate t, JsonGenerator jg, SerializerProvider sp) throws IOException {
        jg.writeString(formatter.format(t));
    }

}

El trabajo lo realiza el método serialize(...) usamos el método jg.writeString(...) para escribir el objeto LocalDate en el formato definido por el patrón "dd::MM::yy".

Para usar el serializer debemos agregarlo al módulo.

JSR310Module module = new JSR310Module();
module.addSerializer(new LocalDateSerializer());

La clase LocalDateSerializar se encarga de escribir el objeto a formato JSON, si deseamos realizar el proceso inverso debemos escribir otra clase, esta vez extendiendo StdDeserializer<LocalDate>, la tarea de esta clase será tomar la cadena JSON y convertirla en un objeto LocalDate, ejemplo:

public class LocalDateDeserializer extends StdDeserializer<LocalDate> {

    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd::MM::yy");

    public LocalDateDeserializer() {
        this(LocalDate.class);
    }

    public LocalDateDeserializer(Class<LocalDate> date) {
        super(date);
    }

    @Override
    public LocalDate deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        return LocalDate.parse(jp.getValueAsString(), formatter);
    }

}

Veamos un ejemplo de como usar estas clases:

LocalDate fecha = LocalDate.of(2010, Month.MARCH, 20);

JSR310Module module = new JSR310Module();
module.addSerializer(new LocalDateSerializer(LocalDate.class));
module.addDeserializer(LocalDate.class, new LocalDateDeserializer());

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

String out = mapper.writeValueAsString(fecha);
LocalDate date = mapper.readValue(out, LocalDate.class);

System.out.println("json format: " + out);
System.out.println("java.time.LocalDate: " + date);

La ventana de salida mostrará lo siguiente:

json format: "20::03::10"
java.time.LocalDate: 2010-03-20

Comentarios

Entradas populares de este blog

Conectar SQL Server con Java

Detección de rostros

Instalar OpenCV para Python en Windows