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 String
s?
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…