Usar JAXB para desmarcar / ordenar una Lista


Estoy tratando de crear un servidor REST muy simple. Solo tengo un método de prueba que devolverá una Lista de cadenas. Aquí está el código:


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

Da el siguiente error:

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

Esperaba que JAXB tuviera una configuración predeterminada para tipos simples como String, Integer, etc. Supongo que no. Esto es lo que me imaginé:


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

¿Cuál es la forma más fácil de hacer que este método funcione?

Author: User1, 2009-10-22

12 answers

Usé el ejemplo de @LiorH y lo expandí a:


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

Tenga en cuenta que usa genéricos para que pueda usarlo con otras clases que no sean String. Ahora, el código de la aplicación es simplemente:


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

¿Por qué no existe esta simple clase en el paquete JAXB? ¿Alguien vio algo parecido en otro lugar?

 46
Author: User1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-21 21:55:51
@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();
}
 31
Author: Sample Code,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-26 09:28:41

En caso de que cualquiera de ustedes quiera escribir una envoltura de lista para listas que contengan elementos de múltiples clases y quiera dar un nombre XmlElement individual de acuerdo con el tipo de Clase sin Escribir clases X Wrapper, podría usar la anotación @XmlMixed. Al hacerlo, JAXB nombra los elementos de la lista de acuerdo con el valor establecido por @XmlRootElement. Al hacerlo, debe especificar qué clases podrían estar en la lista utilizando @XmlSeeAlso

Ejemplo:

Posibles clases en el list

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

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

Clase de envoltura

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

Ejemplo:

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


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

Resultado:

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

Alternativamente puede especificar los nombres de XmlElement directamente dentro de la clase wrapper usando la anotación @XmlElementRef

@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;
    }
}
 12
Author: Zounadire,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-24 11:02:42

Esto se puede hacer MUCHO más fácil usando la biblioteca wonderful XStream. Sin envoltorios, sin anotaciones.

XML de destino

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

Serialización

(String el alias se puede evitar usando la etiqueta string en minúsculas, pero usé el código de 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);

Deserialización

Deserialización 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);

Deserialización en Cadena []

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

Tenga en cuenta que la instancia de XStream es segura para subprocesos y se puede preconfigurar, reduciendo código cantidad a una línea.

XStream también se puede usar como un mecanismo de serialización predeterminado para el servicio JAX-RS. Se puede encontrar un ejemplo de cómo conectar XStream en Jersey aquí

 11
Author: Alex Abdugafarov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-26 18:23:52

Desde un blog personal post, no es necesario crear un objeto JaxbList < T > específico.

Asumiendo un objeto con una lista de cadenas:

@XmlRootElement
public class ObjectWithList {

    private List<String> list;

    @XmlElementWrapper(name="MyList")
    @XmlElement
    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

}

Un viaje de ida y vuelta de JAXB:

public static void simpleExample() throws JAXBException {

    List<String> l = new ArrayList<String>();
    l.add("Somewhere");
    l.add("This and that");
    l.add("Something");

    // Object with list
    ObjectWithList owl = new ObjectWithList();
    owl.setList(l);

    JAXBContext jc = JAXBContext.newInstance(ObjectWithList.class);
    ObjectWithList retr = marshallUnmarshall(owl, jc);

    for (String s : retr.getList()) {
        System.out.println(s);
    } System.out.println(" ");

}

Produce lo siguiente:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
    <MyList>
        <list>Somewhere</list>
        <list>This and that</list>
        <list>Something</list>
    </MyList>
</objectWithList>
 10
Author: Jérôme Verstrynge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-07 18:33:30

He encontrado este patrón un par de veces, he encontrado que la forma más fácil es definir una clase interna con anotaciones JaxB. (de todos modos, es probable que desee definir el nombre de la etiqueta raíz)

Así que su código se vería algo como esto

@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 trabaja con javax.rs (jax-rs) Devolvería el objeto Response con el wrapper establecido como su entidad

 8
Author: LiorH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-21 22:06:21

Finalmente he resuelto usando JacksonJaxbJsonProvider requiere pocos cambios en su Primavera context.xml y Maven pom.xml

En tu Primavera context.xml añade JacksonJaxbJsonProvider al <jaxrs:server>:

<jaxrs:server id="restService" address="/resource">
    <jaxrs:providers>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
    </jaxrs:providers>
</jaxrs:server>

En tu Maven pom.xml add:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-jaxrs</artifactId>
    <version>1.9.0</version>
</dependency>
 3
Author: petrsyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 09:58:34

El ejemplo de User1 funcionó bien para mí. Pero, como advertencia, no funcionará con nada más que simples tipos de cadena / Entero, a menos que agregue una anotación @XmlSeeAlso:

@XmlRootElement(name = "List")
@XmlSeeAlso(MovieTicket.class)
public class MovieTicketList {
    protected List<MovieTicket> list;

Esto funciona bien, aunque me impide usar una sola clase de lista genérica en toda mi aplicación. También podría explicar por qué esta clase aparentemente obvia no existe en el paquete JAXB.

 2
Author: piepera,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-10 15:44:58

Asegúrese de agregar la etiqueta @XmlSeeAlso con sus clases específicas utilizadas dentro de JaxbList. Es muy importante de lo contrario lanza HttpMessageNotWritableException

 0
Author: Maggy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-06 11:15:48

Habría ahorrado tiempo si hubiera encontrado Resteasy Jackson Provider antes.

Simplemente agregue el JAR del proveedor de Resteasy Jackson . Sin envoltorios de entidades. Sin anotaciones XML. No hay escritores de cuerpo de mensajes personalizados.

 0
Author: mstrthealias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-23 19:33:07

Si está utilizando maven en el proyecto jersey, agregue a continuación en pom.xml y actualizar las dependencias del proyecto para que Jaxb pueda detectar la clase del modelo y convertir la lista al tipo de medio XML de la aplicación:

<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-core</artifactId>
    <version>2.2.11</version>
</dependency>
 0
Author: user7455210,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-22 23:42:25

Para una solución más general, para la serialización JAXB-XML de cualquier lista de nivel superior , que solo requiere 1 nueva clase para ser escrita, echa un vistazo a la solución dada en esta pregunta:

¿Es posible configurar JAXB mediante programación?

public class Wrapper<T> {

private List<T> items = new ArrayList<T>();

@XmlAnyElement(lax=true)
public List<T> getItems() {
    return items;
}

}

//JAXBContext is thread safe and so create it in constructor or 
//setter or wherever:
... 
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
... 

public String marshal(List<T> things, Class clazz) {

  //configure JAXB and marshaller     
  Marshaller m = jc.createMarshaller();
  m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

  //Create wrapper based on generic list of objects
  Wrapper<T> wrapper = new Wrapper<T>(things);
  JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);

  StringWriter result = new StringWriter();
  //marshal!
  m.marshal(wrapperJAXBElement, result);

  return result.toString();

}
 0
Author: Gonen I,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-17 12:29:14