Using JAXB to unmarshal/marshal a List<String>

Viewed 128843

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?

12 Answers

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

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.

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

}
Related