web-dev-qa-db-fra.com

Comment personnaliser la sérialisation d'une liste d'objets JAXB en JSON?

J'utilise Jersey pour créer un service Web REST pour un composant serveur.

L'objet annoté JAXB que je veux sérialiser dans une liste ressemble à ceci:

@XmlRootElement(name = "distribution")
@XmlType(name = "tDistribution", propOrder = {
    "id", "name"
})
public class XMLDistribution {
    private String id;
    private String name;
    // no-args constructor, getters, setters, etc
}

J'ai une ressource REST pour récupérer une distribution qui ressemble à ceci:

@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))
}

J'ai également une ressource REST pour récupérer une liste de toutes les distributions, qui ressemble à ceci:

@Path("/distributions")
public class RESTDistributions {
    @GET
    @Produces("application/json")
    public List<XMLDistribution> retrieveAll() {
        return retrieveDistributions();
    }
    // business logic (retrieveDistributions())
}

J'utilise un ContextResolver pour personnaliser la sérialisation JAXB, qui est actuellement configurée comme ceci:

@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;
    }
}

Les deux ressources REST fonctionnent, ainsi que le résolveur de contexte. Ceci est un exemple de sortie pour le premier:

// path: /distribution/1
{"id":1,"name":"Example Distribution"}

C'est exactement ce que je veux. Voici un exemple de sortie pour la liste:

// path: /distributions
{"distribution":[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]}

Ce qui n'est pas tout à fait ce que je veux.

Je ne comprends pas pourquoi il y a une balise distribution englobante. Je voulais le supprimer avec .rootUnwrapping(true) dans le résolveur de contexte, mais apparemment, cela n'enlève qu'une autre balise englobante. Voici la sortie avec .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"}]}}

J'ai également dû configurer .arrays("distribution") pour toujours obtenir un tableau JSON, même avec un seul élément.

Idéalement, j'aimerais avoir ceci en sortie:

// path: /distribution/1
{"id":1,"name":"Example Distribution"} // currently works
// path: /distributions
[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]

J'ai essayé de renvoyer un List<XMLDistribution>, Un XMLDistributionList (enrouler autour d'une liste), un XMLDistribution[], Mais je n'ai pas trouvé de moyen d'obtenir un simple tableau JSON de distributions dans mon format requis.

J'ai également essayé les autres notations renvoyées par JSONConfiguration.natural(), JSONConfiguration.mappedJettison(), etc., et je n'ai rien obtenu qui ressemble à ce dont j'ai besoin.

Est-ce que quelqu'un sait s'il est possible de configurer JAXB pour ce faire?

54
Alpha Hydrae

J'ai trouvé une solution: remplacer le sérialiseur JAXB JSON par un sérialiseur JSON mieux comporté comme Jackson. Le moyen le plus simple est d'utiliser jackson-jaxrs, qui l'a déjà fait pour vous. La classe est JacksonJsonProvider. Tout ce que vous avez à faire est de modifier le fichier web.xml de votre projet afin que Jersey (ou une autre implémentation JAX-RS) le recherche. Voici ce que vous devez ajouter:

<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>

Et c'est tout ce qu'il y a à faire. Jackson sera utilisé pour la sérialisation JSON, et cela fonctionne comme vous l'attendez pour les listes et les tableaux.

Le plus long est d'écrire votre propre MessageBodyWriter personnalisé enregistré pour produire "application/json". Voici un exemple:

 @ Fournisseur 
 @ Produit ("application/json") 
 Classe publique JsonMessageBodyWriter implémente MessageBodyWriter {
 @Override 
 Public long getSize (Object obj , Type de classe, Type genericType, 
 Annotation [] annotations, MediaType mediaType) {
 Return -1; 
} 
 
 @Override 
 public boolean isWriteable (Type de classe, Type genericType, 
 Annotations d'annotation [], MediaType mediaType) {
 return true; 
} 
 
 @Override 
 Public void writeTo (Object target, Class type, Type genericType, 
 Annotation [] annotations, MediaType mediaType, 
 MultivaluedMap httpHeaders, OutputStream outputStream) 
 lève IOException {
 new ObjectMapper (). writeValue (outputStream, target); 
} 
} 

Vous devrez vous assurer que votre fichier web.xml inclut le package, comme pour la solution prête à l'emploi ci-dessus.

Quoi qu'il en soit: le tour est joué! Vous verrez JSON correctement formé.

Vous pouvez télécharger Jackson ici: http://jackson.codehaus.org/

102
Jonathan

La réponse de Jonhatan est excellente et elle m'a été très utile.

Juste une mise à niveau:

si vous utilisez la version 2.x de Jackson (par exemple la version 2.1), la classe est com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider, donc le web.xml est:

<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>
13
Gabriele