47 votes

Utilisation de JAXB pour démarshaliser/marshaliser une liste<string>.

J'essaie de créer un serveur REST très simple. J'ai juste une méthode de test qui renvoie une liste de chaînes de caractères. Voici le code :

@GET
@Path("/test2")
public List test2(){
    List list=new Vector();
    list.add("a");
    list.add("b");
    return list;
}

Il donne l'erreur suivante :

SEVERE: A message body writer for Java type,
class java.util.Vector, and MIME media type,
application/octet-stream, was not found

J'espérais que JAXB avait un paramètre par défaut pour les types simples tels que String, Integer, etc. Je suppose que non. Voici ce que j'ai imaginé :

<Strings>
  <String>a</String>
  <String>b</String>
</Strings>

Quel est le moyen le plus simple de faire fonctionner cette méthode ?

0 votes

46voto

User1 Points 8352

J'ai utilisé l'exemple de @LiorH et l'ai étendu à :

@XmlRootElement(name="List")
public class JaxbList<T>{
    protected List<T> list;

    public JaxbList(){}

    public JaxbList(List<T> list){
        this.list=list;
    }

    @XmlElement(name="Item")
    public List<T> getList(){
        return list;
    }
}

Notez qu'il utilise des génériques et que vous pouvez donc l'utiliser avec d'autres classes que String. Maintenant, le code de l'application est simplement :

    @GET
    @Path("/test2")
    public JaxbList test2(){
        List list=new Vector();
        list.add("a");
        list.add("b");
        return new JaxbList(list);
    }

Pourquoi cette classe simple n'existe-t-elle pas dans le paquetage JAXB ? Quelqu'un a vu quelque chose de semblable ailleurs ?

1 votes

En essayant de démarshaller ceci avec un proxy cxf, on obtient une exception java.lang.NullPointerException at com.sun.xml.bind.v2.runtime.reflect.Lister$CollectionLister.addToPack(Lister.java:305).

0 votes

Cela n'a pas fonctionné pour moi non plus. J'ai eu une erreur : Pas de rédacteur pour JaxbList. Finalement, j'ai résolu le problème en utilisant JacksonJaxbJsonProvider. Aucun code Java ne doit être modifié. Juste quelques changements dans le context.xml de Spring et le pom.xml de Maven, cf. stackoverflow.com/a/30777172/1245231

32voto

Sample Code Points 261
@GET
@Path("/test2")
public Response test2(){
   List<String> list=new Vector<String>();
   list.add("a");
   list.add("b");

   final GenericEntity<List<String>> entity = new GenericEntity<List<String>>(list) { };
   return Response.ok().entity(entity).build();
}

7 votes

Toujours utile 2 ans plus tard :)

5 votes

Toujours utile 3 ans plus tard :)

2 votes

Cela ne fonctionne pas pour moi. J'utilise Jersey 1.11 avec Glasfish 3.1.2. J'obtiens javax.ws.rs.WebApplicationException: com.sun.jersey.api.MessageException: A message body writer for Java class java.util.Vector, and Java type java.util.List<java.lang.String>, and MIME media type application/json was not found

13voto

Zounadire Points 110

Si l'un d'entre vous veut écrire un wrapper de liste pour des listes contenant des éléments de plusieurs classes et souhaite donner à un XmlElement individuel un nom en fonction du type de classe sans écrire de classes Wrapper X, vous pouvez utiliser la méthode suivante @XmlMixed l'annotation. Ce faisant, JAXB nomme les éléments de la liste en fonction de la valeur définie par l'annotation @XmlRootElement . Pour ce faire, vous devez spécifier les classes qui pourraient éventuellement figurer dans la liste en utilisant la commande @XmlSeeAlso

Exemple :

Classes possibles dans la liste

@XmlRootElement(name="user")
public class User {/*...*/}

@XmlRootElement(name="entry")
public class LogEntry {/*...*/}

Classe enveloppante

@XmlRootElement(name="records")
@XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{

    protected List<T> records;

    public JaxbList(){}

    public JaxbList(List<T> list){
        this.records=list;
    }

    @XmlMixed 
    public List<T> getRecords(){
        return records;
    }
}

Exemple :

List l = new List();
l.add(new User("userA"));
l.add(new LogEntry(new UserB()));

XStream xStream = new XStream();
String result = xStream.toXML(l);

Résultat :

<records>
    <user>...</user>
    <entry>...</entry>
</records>

Sinon, vous pouvez spécifier les noms des éléments Xml directement dans la classe wrapper en utilisant l'attribut @XmlElementRef annotation

@XmlRootElement(name="records")
@XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{

    protected List<T> records;

    public JaxbList(){}

    public JaxbList(List<T> list){
        this.records=list;
    }

    @XmlElementRefs({
        @XmlElementRef(name="item", type=Object.class),
        @XmlElementRef(name="user", type=User.class),
        @XmlElementRef(name="entry", type=LogEntry.class)
    })
    public List<T> getRecords(){
        return records;
    }
}

11voto

Frozen Spider Points 2369

Cela peut être fait BEAUCOUP plus facile grâce à l'utilisation de merveilleux XStream bibliothèque. Pas d'emballage, pas d'annotations.

XML cible

<Strings>
  <String>a</String>
  <String>b</String>
</Strings>

Sérialisation

( String peut être évité en utilisant des minuscules string mais j'ai utilisé le code de l'OP)

List <String> list = new ArrayList <String>();
list.add("a");
list.add("b");

XStream xStream = new XStream();
xStream.alias("Strings", List.class);
xStream.alias("String", String.class);
String result = xStream.toXML(list);

Désérialisation

Désérialisation en ArrayList

XStream xStream = new XStream();
xStream.alias("Strings", ArrayList.class);
xStream.alias("String", String.class);
xStream.addImplicitArray(ArrayList.class, "elementData");
List <String> result = (List <String>)xStream.fromXML(file);

Désérialisation en String[]

XStream xStream = new XStream();
xStream.alias("Strings", String[].class);
xStream.alias("String", String.class);
String[] result = (String[])xStream.fromXML(file);

Notez que l'instance XStream est thread-safe et peut être préconfigurée, ce qui réduit la quantité de code à une ligne.

XStream peut également être utilisé comme mécanisme de sérialisation par défaut pour le service JAX-RS. Vous trouverez un exemple de l'intégration de XStream dans Jersey à l'adresse suivante aquí

0 votes

Pouvez-vous me dire s'il est possible d'utiliser les noms comme alias pour les classes par défaut ? J'ai une classe très complexe, elle contient beaucoup d'objets de classes qui contiennent des objets de classes qui contiennent des objets de classes et ainsi de suite... Existe-t-il un moyen d'éviter les milliers d'appels à xStream.alias(...) ? Merci

1 votes

En fait, vous pouvez sérialiser votre classe sans aucun alias, ou avec un simple alias vers une classe racine. Il nommera le tag Root comme vous l'avez aliasé (ou utilisera le nom de classe entièrement qualifié si vous ne l'avez pas fait) et nommera tous les tags internes de la même manière que les champs internes des classes sont nommés. Bonne lecture !

0 votes

Le fait est que le nom complet de la classe (y compris le paquet) est indésirable pour moi. Je veux juste le nom et ne pas définir d'alias pour toutes les classes utilisées, car cela prendrait beaucoup d'espace et de temps. Pouvez-vous me conseiller ?

8voto

LiorH Points 4623

J'ai rencontré ce modèle à plusieurs reprises, j'ai trouvé que la manière la plus simple est de définir une classe interne avec des annotations JaxB. (de toute façon, vous voudrez probablement définir le nom de la balise Root)

donc votre code ressemblerait à quelque chose comme ceci

@GET
@Path("/test2")
public Object test2(){
   MyResourceWrapper wrapper = new MyResourceWrapper();
   wrapper .add("a");
   wrapper .add("b");
   return wrapper ;
}

@XmlRootElement(name="MyResource")
private static class MyResourceWrapper {
       @XmlElement(name="Item")
       List<String> list=new ArrayList<String>();
       MyResourceWrapper (){}

       public void add(String s){ list.add(s);}
 }

si vous travaillez avec javax.rs (jax-rs), je renverrais l'objet Response avec le wrapper comme entité.

0 votes

Semble être un bon début. Mais ça n'a pas l'air très sûr.

0 votes

Vous pouvez supprimer le modificateur statique de la classe interne si la même instance de la classe supérieure peut servir plus d'un thread.

0 votes

Je fais probablement quelque chose de mal. Je ne sais pas comment utiliser l'objet Response que vous avez mentionné. J'ai donc utilisé le code ci-dessus et j'ai obtenu : javax.ws.rs.WebApplicationException : com.sun.xml.bind.v2.runtime.IllegalAnnotationsException : 1 compte d'IllegalAnnotationExceptions XmlElementRef pointe vers une classe inexistante. Une idée ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X