Como posso personalizar a serialização de uma lista de objetos JAXB para JSON?
-
25-09-2019 - |
Pergunta
Estou usando o Jersey para criar um serviço Web REST para um componente do servidor.
O objeto anotado por Jaxb que eu quero serializar em uma lista é assim:
@XmlRootElement(name = "distribution")
@XmlType(name = "tDistribution", propOrder = {
"id", "name"
})
public class XMLDistribution {
private String id;
private String name;
// no-args constructor, getters, setters, etc
}
Eu tenho um recurso de descanso para recuperar uma distribuição que se parece com o seguinte:
@Path("/distribution/{id: [1-9][0-9]*}")
public class RESTDistribution {
@GET
@Produces("application/json")
public XMLDistribution retrieve(@PathParam("id") String id) {
return retrieveDistribution(Long.parseLong(id));
}
// business logic (retrieveDistribution(long))
}
Eu também tenho um recurso de descanso para recuperar uma lista de todas as distribuições, que se parecem com a seguinte:
@Path("/distributions")
public class RESTDistributions {
@GET
@Produces("application/json")
public List<XMLDistribution> retrieveAll() {
return retrieveDistributions();
}
// business logic (retrieveDistributions())
}
Eu uso um contextresolver para personalizar a serialização do JAXB, que está atualmente configurada como esta:
@Provider
@Produces("application/json")
public class JAXBJSONContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
public JAXBJSONContextResolver() throws Exception {
JSONConfiguration.MappedBuilder b = JSONConfiguration.mapped();
b.nonStrings("id");
b.rootUnwrapping(true);
b.arrays("distribution");
context = new JSONJAXBContext(b.build(), XMLDistribution.class);
}
@Override
public JAXBContext getContext(Class<?> objectType) {
return context;
}
}
Ambos os recursos de repouso funcionam, bem como o resolvedor de contexto. Este é um exemplo de saída para o primeiro:
// path: /distribution/1
{"id":1,"name":"Example Distribution"}
Que é exatamente o que eu quero. Este é um exemplo de saída para a lista:
// path: /distributions
{"distribution":[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]}
O que não é exatamente o que eu quero.
Eu não entendo por que há um anexo distribution
etiqueta lá. Eu queria removê -lo com .rootUnwrapping(true)
no resolvedor de contexto, mas aparentemente isso remove apenas outra tag anexando. Esta é a saída com .rootUnwrapping(false)
:
// path: /distribution/1
{"distribution":{"id":1,"name":"Example Distribution"}} // not ok
// path: /distributions
{"xMLDistributions":{"distribution":[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]}}
Eu também tive que configurar .arrays("distribution")
Para sempre obter uma matriz JSON, mesmo com apenas um elemento.
Idealmente, eu gostaria de ter isso como uma saída:
// path: /distribution/1
{"id":1,"name":"Example Distribution"} // currently works
// path: /distributions
[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]
Eu tentei devolver um List<XMLDistribution>
, uma XMLDistributionList
(invólucro em torno de uma lista), um XMLDistribution[]
, mas não consegui encontrar uma maneira de obter uma simples variedade de distribuições JSON no meu formato necessário.
Eu também tentei as outras anotações devolvidas por JSONConfiguration.natural()
, JSONConfiguration.mappedJettison()
, etc, e não conseguia nada parecido com o que eu preciso.
Alguém sabe se é possível configurar o JAXB para fazer isso?
Solução
Encontrei uma solução: substitua o serializador Jaxb JSON por um serializador JSON melhor comportado como Jackson. A maneira mais fácil é usar Jackson-Jaxrs, que já fez isso para você. A classe é JacksonjsonProvider. Tudo o que você precisa fazer é editar o web.xml do seu projeto para que Jersey (ou outra implementação Jax-RS) digitalize. Aqui está o que você precisa adicionar:
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>your.project.packages;org.codehaus.jackson.jaxrs</param-value>
</init-param>
E isso é tudo o que há para isso. Jackson será usado para a serialização do JSON e funciona da maneira que você espera para listas e matrizes.
A maneira mais longa é escrever seu próprio MessageBodyWriter personalizado registrado para produzir "Application/JSON". Aqui está um exemplo:
@Provider @Produces("application/json") public class JsonMessageBodyWriter implements MessageBodyWriter { @Override public long getSize(Object obj, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } @Override public boolean isWriteable(Class type, Type genericType, Annotation annotations[], MediaType mediaType) { return true; } @Override public void writeTo(Object target, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream outputStream) throws IOException { new ObjectMapper().writeValue(outputStream, target); } }
Você precisará garantir que seu web.xml inclua o pacote, como para a solução pronta acima.
De qualquer maneira: pronto! Você verá JSON corretamente formado.
Você pode baixar Jackson aqui:http://jackson.codehaus.org/
Outras dicas
A resposta de Jonhatan é ótima e tem sido muito útil para mim.
Apenas uma atualização:
Se você usar a versão 2.x de Jackson (por exemplo, versão 2.1), a classe é com.fasterxml.jackson.jaxrs.json.jacksonjaxbjsonprovider, portanto o web.xml é:
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>your.project.packages;com.fasterxml.jackson.jaxrs.json</param-value>
</init-param>