Using JAXB to unmarshal/marshal a List<String>
Asked Answered
E

12

47

I'm trying to create a very simple REST server. I just have a test method that will return a List of Strings. Here's the code:


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

It gives the following error:

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

I was hoping JAXB had a default setting for simple types like String, Integer, etc. I guess not. Here's what I imagined:


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

What's the easiest way to make this method work?

Eanes answered 21/10, 2009 at 20:29 Comment(1)
Similar: JAXB: How to marshal objects in lists?Salvidor
E
47

I used @LiorH's example and expanded it to:


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

Note, that it uses generics so you can use it with other classes than String. Now, the application code is simply:


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

Why doesn't this simple class exist in the JAXB package? Anyone see anything like it elsewhere?

Eanes answered 21/10, 2009 at 21:55 Comment(2)
trying to unmarshall this with a cxf proxy led to java.lang.NullPointerException at com.sun.xml.bind.v2.runtime.reflect.Lister$CollectionLister.addToPack(Lister.java:305)Bosson
This did not work for me as well. I got an error: No writer for JaxbList. Finally I've solved it using JacksonJaxbJsonProvider. No Java code needs to be modified. Just few changes in Spring context.xml and Maven pom.xml, see https://mcmap.net/q/282221/-using-jaxb-to-unmarshal-marshal-a-list-lt-string-gtPresumable
W
32
@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();
}
Wilton answered 26/7, 2010 at 9:28 Comment(3)
Still useful 2 years later :)Rus
Still useful 3 years later :)Seaman
This does not work for me. Using Jersey 1.11 w/Glasfish 3.1.2. I get 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 foundIzabel
P
13

In case anyone of you wants to write a list wrapper for lists containing elements of multiple classes and want to give an individual XmlElement name according to the Class type without Writing X Wrapper classes you could use the @XmlMixed annotation. By doing so JAXB names the items of the list according to the value set by the @XmlRootElement. When doing so you have to specify which classes could possibly be in the list using @XmlSeeAlso

Example:

Possible Classes in the list

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

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

Wrapper class

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

Example:

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


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

Result:

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

Alternatevily you could specify the XmlElement names directly inside the wrapper class using the @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;
    }
}
Papillote answered 6/3, 2012 at 8:53 Comment(0)
C
12

From a personal blog post, it is not necessary to create a specific JaxbList < T > object.

Assuming an object with a list of strings:

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

}

A JAXB round trip:

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(" ");

}

Produces the following:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
    <MyList>
        <list>Somewhere</list>
        <list>This and that</list>
        <list>Something</list>
    </MyList>
</objectWithList>
Coppock answered 7/8, 2012 at 18:33 Comment(0)
F
11

This can be done MUCH easier using wonderful XStream library. No wrappers, no annotations.

Target XML

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

Serialization

(String alias can be avoided by using lowercase string tag, but I used OP's code)

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

Deserialization

Deserialization into 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);

Deserialization into String[]

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

Note, that XStream instance is thread-safe and can be pre-configured, shrinking code amount to one-liners.

XStream can also be used as a default serialization mechanism for JAX-RS service. Example of plugging XStream in Jersey can be found here

Fax answered 8/12, 2011 at 7:37 Comment(4)
Can you please tell if there is an ability to use names as alias for classes by default? I have a very complex class, it contains lots of classes objects that contain classes objects that contain classes objects and so on... Is there any way to avoid thousends of calling xStream.alias(...)? ThanksRelator
Actually, you may serialize your class without any aliases at all, or with just alias to a root class. It will name the root tag as you've aliased (or use fully qualified class name if you didn't) and name all inner tags the same way inner fields of classes are named. Enjoy!Fax
The point is that the full name of class (including package) is unwanted for me. I want just name and not to define aliases for all used classes, because it will take a lot of space and time. Can you advice me something?Relator
As I said, alias only the root class and check XStream generated XML.Fax
F
8

I have encountered this pattern a few times, I found that the easiest way is to define an inner class with JaxB annotations. (anyways, you'll probably want to define the root tag name)

so your code would look something like this

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

if you work with javax.rs (jax-rs) I'd return Response object with the wrapper set as its entity

Fluker answered 21/10, 2009 at 20:42 Comment(6)
looks like a great start. But this doesn't look thread safe.Eanes
you can remove the static modifier from the inner class if same instance of the top class can serve more than one thread.Fluker
I'm probably doing something wrong. I'm not sure how to use the Response object you mentioned. So, I just used the code above and got: javax.ws.rs.WebApplicationException: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions XmlElementRef points to a non-existent class. Any ideas?Eanes
you are right, my mistake, trying to write code without running it. i fixed it.Fluker
The inner class must be static, or the following error will occur: ResourceWrapper is a non-static inner class, and JAXB can't handle those.Mecklenburg
Still Working after 4 years !! HATS OFF !!Odetteodeum
P
3

Finally I've solved it using JacksonJaxbJsonProvider It requires few changes in your Spring context.xml and Maven pom.xml

In your Spring context.xml add JacksonJaxbJsonProvider to the <jaxrs:server>:

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

In your Maven pom.xml add:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-jaxrs</artifactId>
    <version>1.9.0</version>
</dependency>
Presumable answered 11/6, 2015 at 9:44 Comment(0)
S
2

User1's example worked well for me. But, as a warning, it won't work with anything other than simple String/Integer types, unless you add an @XmlSeeAlso annotation:

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

This works OK, although it prevents me from using a single generic list class across my entire application. It might also explain why this seemingly obvious class doesn't exist in the JAXB package.

Syndesis answered 10/3, 2010 at 15:44 Comment(0)
G
0

Make sure to add @XmlSeeAlso tag with your specific classes used inside JaxbList. It is very important else it throws HttpMessageNotWritableException

Gauffer answered 6/6, 2013 at 11:15 Comment(0)
T
0

I would've saved time if I found Resteasy Jackson Provider sooner.

Just add the Resteasy Jackson Provider JAR. No entity wrappers. No XML annotations. No custom message body writers.

Thorvald answered 23/4, 2015 at 19:33 Comment(0)
R
0

If you are using maven in the jersey project add below in pom.xml and update project dependencies so that Jaxb is able to detect model class and convert list to Media type application XML:

<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-core</artifactId>
    <version>2.2.11</version>
</dependency>
Rutland answered 22/1, 2017 at 23:42 Comment(0)
T
0

For a more general solution, for JAXB-XML serialization of any top level list , which only requires 1 new class to be written, check out the solution given in this question:

Is it possible to programmatically configure JAXB?

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();

}
Tishatishri answered 17/5, 2018 at 12:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.