Pacote java.time com JAX-RS no Wildfly 8.2

Temos a seguinte API de lista de tarefas feita com JAX-RS:

@Path("/tarefas")
public class TarefasResource {
  @Inject
  private TarefasRepository repo;

  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  public Response nova(Tarefa t) throws URISyntaxException {
    repo.cria(t);
    return Response.created(new URI("/tarefas/" + t.getId()))
      .entity(t)
      .build();
  }

  @GET
  @Path("/{id}")
  @Produces(MediaType.APPLICATION_JSON)
  public Tarefa lista(@PathParam("id") Integer id) {
    return repo.busca(id);
  }
}

Uma tarefa tem as propriedades id, do tipo Integer descricao, do tipo String e data, do tipo LocalDate do pacote java.time:

public class Tarefa {
  private Integer id;
  private LocalDate data;
  private String descricao;
  //getters e setters...
}

A API de estilo REST define o recurso /tarefas. Para criar uma nova tarefa deve ser enviado um POST para a URI /tarefas com uma representação da tarefa em JSON. Para obter um JSON com a tarefa de id 1, deve ser enviado um GET para a URI /tarefas/1.

O código acima foi implantado em um Wildfly 8.2.0.Final.

Problemas ao serializar objetos do java.time de/para JSON

Mas há algo de estranho.

Ao enviarmos um GET para /tarefas/1, obtemos o seguinte JSON:

{
  "id": 1,
  "descricao": "Configurar JAX-RS",
  "data": {"year":2016,"month":"FEBRUARY","chronology":{"calendarType":"iso8601","id":"ISO"},"era":"CE","dayOfYear":56,"dayOfWeek":"THURSDAY","leapYear":true,"dayOfMonth":25,"monthValue":2}
}

A representação do LocalDate em JSON ficou gigantesca! Vários detalhes internos foram exibidos…

E se tentarmos enviar um POST para /tarefas com o JSON abaixo?

{
  "data": "2016-02-26",
  "descricao": "Configurar Wildfly"
}

O resultado será um erro 400 (Bad Request) com a seguinte mensagem:

com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDate] from String value ('2016-02-26'); no single-String constructor/factory method
 at [Source: io.undertow.servlet.spec.ServletInputStreamImpl@1c6dc29c; line: 1, column: 2] (through reference chain: br.com.alexandreaquiles.modelo.Tarefa["data"])

A mensagem de erro acima informa que o Jackson, a biblioteca de serialização JSON usada pelo Wildfly, não conseguiu transformar a String em um LocalDate.

Como fazer para ensinar o Jackson a trabalhar com um objeto do tipo LocalDate ou de outras classes do pacote java.time como se fossem Strings?

Melhorando a (de)serialização

O Wildfly 8.2.0.Final usa a versão 2.4.1 do Jackson, que tem a extensão jackson-datatype-jsr310, responsável por serializações mais interessantes de/para classes do pacote java.time.

Se estivermos usando o Maven, basta adicionar mais uma dependência:

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

Outras ferramentas de gerenciamento de dependência teriam configurações análogas. Caso não esteja usando nenhuma ferramenta do tipo, baixe o jar.

Ao testarmos novamente, os mesmos erros acontecem… Que chato!

É que precisamos configurar um ContextResolver, anotando-o com @Provider e registrando o módulo JSR310Module do Jackson:

@Provider
public class JacksonJavaTimeConfiguration implements ContextResolver<ObjectMapper> {
  private final ObjectMapper mapper;

  public JacksonJavaTimeConfiguration() {
    mapper = new ObjectMapper();
    mapper.registerModule(new JSR310Module());
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
  }

  @Override
  public ObjectMapper getContext(Class<?> type) {
    return mapper;
  }
}

Note a configuração WRITE_DATES_AS_TIMESTAMPS setada para false. Se não fizermos isso, as datas são representadas como um array no JSON, ao invés de um texto.

Agora, se enviarmos um GET para /tarefas/1, obtemos:

{
  "id": 1,
  "descricao": "Configurar JAX-RS",
  "data": "2016-02-25";
}

O LocalDate passa a ser representado no JSON como uma String no formato ISO-8601.

Ao enviarmos o POST para /tarefas novamente, tudo dá certo! Recebemos um status 201 (Created).

Funciona em outro servidor de aplicação?

Infelizmente, não!

A grande questão é que nossa configuração foi feita para a biblioteca usada pelo Wildfly, o Jackson, inclusive em uma versão específica.

O Glassfish, por exemplo, usa a biblioteca MOXy para serialização de/para JSON. As configurações seriam diferentes…

Anúncios