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…