Spring Boot series: autoconfiguración de Jackson

Para comenzar este año 2015, vuelvo con los minipost sobre Spring Boot. Esta vez explicaré, con un ejemplo, como configurar el parseo de objetos a json y viceversa con Jackson, muy fácilmente en Spring Boot, sobre todo desde la versión 1.2 de este.

Concepto:
Jackson es una librería Java muy potente para el procesamiento / parseo de objetos a json y viceversa. Ofrece muchas opciones y posibilidades, anotaciones propias, etc. Suele ser casi la librería estándar para esto en los proyectos en los que he trabajado.

En un típico proyecto Spring, como los que he explicado en otros posts, como el de implementar un servicio REST con Spring, suele venir por defecto Jackson.
También en el post de implementar un servicio REST con JAX-RS usé Jackson para lo propio.

Para el caso de Spring, suele ser suficiente con cargar en el contexto un Bean de Jackson (suele ser un ObjectMapper), configurado tal cual lo necesitemos, y ya automáticamente nuestros REST usarán ese «mapper».

Para este ejemplo voy a explicar como configurar Jackson en Spring Boot 1.2 (es más facil aún en esta nueva versión), para el uso de fechas con la librería Joda-time, así como también para fechas con la nueva API de Java 8 (la famosa especificación JSR-310).

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.5
IDE Intellij 14.0.2 Ultimate version
Lombok

Pasos:

Vamos a implementar una pequeña aplicación Spring Boot, con un entity Info y un controller que será un mini servicio REST: vamos a implementar solo el GET de un elemento y el POST, sin servicio ni repositorios. Esto no lo haríamos así si quisiéramos tener una implementación por capas correcta, claro :P. Pero para nuestro ejemplo, es suficiente.
Esta entity Info tendrá un atributo de tipo fecha y hora (LocalDateTime de joda-time primero, y LocalDateTime de la API Java 8 Date después). Y para simplificar el código, usaremos Lombok
El proyecto completo os lo podéis descargar como siempre de mi github, proyecto springboot-series-jackson. Está montado con la librería joda-time, pero también está comentado para usar la librería JSR-310.

¡Al lio!

Con Joda-time

1. Añadimos a nuestro pom.xml de maven lo necesario para montar una aplicación Spring Boot básica:

    ...
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        ...
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>               
    ...

Lo de siempre: la dependencia «padre» del proyecto Spring Boot, la dependencia para el starter web y el plugin para maven (para poder arrancarlo). Nada nuevo.

2. Añadimos la dependencia de la librería joda, así como de la sublibrería jackson para joda:

    ...
    <dependencies>
        ...
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-joda</artifactId>
            <version>2.4.4</version>
        </dependency>
        ...
    </dependencies>
    ...

La librería core de jackson ya nos la incluye Spring, solo necesitamos añadirle la libreria «third party» para el tipo joda-time.
En nuestro pom.xml añadimos también lombok, junit y alguna otra librería. El pom.xml completo lo tenéis aquí.

3. Creamos nuestra clase Application para el arranque de Spring Boot:

package com.edwise.springbootseries.jackson;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

La anotación @SpringBootApplication es nueva en la versión 1.2. Simplemente es un ‘atajo’ para las tres anotaciones @ComponentScan, @EnableAutoConfiguration y @Configuration. Nada más.

4. Creamos nuestra clase entity, Info:

package com.edwise.springbootseries.jackson.entity;

import lombok.Data;
import lombok.experimental.Accessors;
import org.joda.time.LocalDateTime;

@Data
@Accessors(chain = true)
public class Info {

    private long id;
    private String info;
    private LocalDateTime creationDateTime;
}

Consta solo de tres atributos, un long, un String y un LocalDateTime. Usamos lombok para que genere el código típico (getters, setter, toString…).

5. Creamos nuestro RestController, solo con un GET y un POST, mockeados (ni servicio, ni repositorio, etc):

package com.edwise.springbootseries.jackson.controller;

import com.edwise.springbootseries.jackson.entity.Info;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.joda.time.LocalDateTime;

@RestController
@RequestMapping("/api/info/")
public class InfoController {

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Info getInfo(@PathVariable long id) {
        return new Info()
                .setId(id)
                .setInfo("Info 1234")
                .setCreationDateTime(new LocalDateTime(2001, 12, 12, 13, 40, 30));
    }

    @RequestMapping(method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity createInfo(@RequestBody Info info) {
        return new ResponseEntity<>(info.setId(1234), HttpStatus.CREATED);
    }
}

Nada nuevo tampoco: un método para obtener con GET un Info, por id, y un método para crear un Info completo, por POST.

6. Si probamos a arrancar nuestra aplicación Spring Boot, y accedemos (mediante navegador o cliente REST mejor) a http://localhost:8080/api/info/1, nos devolverá algo como esto:

{
  "id": 1,
  "info": "Info 1234",
  "creationDateTime": [
    2001,
    12,
    12,
    13,
    40,
    30,
    0
  ]
}

Si, un json. Spring Boot nos carga automáticamente en el contexto un bean de jackson para realizar ese parseo. Además, con la nueva versión 1.2, solo por tener en el proyecto la librería jackson de joda (jackson-datatype-joda), también nos carga el JodaModule que usábamos en los posts de montar un servicio REST el solito. No tenemos que hacer nada más.

7. El formato en el que se muestra el atributo fecha por defecto es una especie de array con los valores. Si queremos que sea algo como esto: «2001-12-12T13:40:30.000», en principio tendríamos que añadir al mapper la configuración siguiente, como ya hicimos en alguno de los posts anteriores:

   ...
   objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
   ...

No es necesario hacerlo así. Esto también ha sido simplificado en Spring Boot, y lo único que tenemos que hacer es añadir, en un archivo .properties (o .yml) la configuración que queramos. En este caso, crearíamos un application.properties bajo una carpeta ‘resources’:

spring.jackson.serialization.write_dates_as_timestamps=false

Si probamos a arrancar la aplicación, y accedemos otra vez http://localhost:8080/api/info/1, ahora nos devolverá algo así:

{
  "id": 1,
  "info": "Info 1234",
  "creationDateTime": "2001-12-12T13:40:30.000"
}

Resumiendo: hemos configurado Jackson, con joda-time, y con un formato concreto de fecha, simplemente añadiendo las librerías al proyecto y añadiendo una propiedad a un fichero properties. El resto, se ha encargado Spring Boot 🙂

Con la API Date Java 8 (JSR-310)

Los pasos son casí los mismos que en el anterior, solo voy a detallar los que cambian:

2. Añadimos al pom la dependencia de la librería jackson la nueva api Date en Java 8:

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

4. Creamos nuestra entity Info, pero ahora con el LocalDateTime de Java 8… no cambia nada más que el import:

...
import java.time.LocalDateTime;
...

5. Creamos nuestro RestController: solo cambia el import del LocalDateTime, y la forma en que lo creamos en el setter:

...
import java.time.LocalDateTime;

@RestController
@RequestMapping("/api/info/")
public class InfoController {

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Info getInfo(@PathVariable long id) {
        return new Info()
                .setId(id)
                .setInfo("Info 1234")
                .setCreationDateTime(LocalDateTime.of(2001, 12, 12, 13, 40, 30));
    }

    ...
}

¡Y listo! el resto de pasos son exactamente iguales: añadiríamos la misma propiedad a nuestro application.properties, y ya tendríamos Jackson funcionando en nuestra aplicación Spring Boot, esta vez usando la nueva API Date de Java 8. Y, de la misma manera, lo único que hemos necesitado para configurarla es añadir la librería jackson de la JSR-310 al proyecto, añadir esa propiedad a nuestro properties.

Como comente antes, el proyecto completo está en mi github, proyecto springboot-series-jackson 🙂