I try to return a list of Strings in Jersey as JSON and XML.
I thought this would be trivial.
My first try was to write something like this
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/bar")
public List<String> get() {
return dao.get();
}
and I expected an output similiar to this: ["string1", ..., "stringN]
unfortunately I got this:
com.sun.jersey.api.MessageException: A message body writer for Java class java.util.LinkedList, and Java type java.util.List<java.lang.String>, and MIME media type application/json was not found
Then I wrote a wrapper StringList for List
@XmlRootElement
public class StringList {
private List<String> data;
public StringList() {
}
public StringList(List<String> data) {
this.data = data;
}
public List<String> getData() {
return data;
}
public void setData(List<String> data) {
this.data = data;
}
}
and modified the facade to
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/foo")
public StringList stringlist() {
return new StringList(sl());
}
Which works great for Lists with more items than 1.
{"data":["foo","bar"]}
Unfortunately I get two unexepected results for one or zero elements
{"data": "just one"} // for one element i would expect {"data": ["just one"]}
null // for no elements i would expect {"data": []}
Am I doing something completly wrong?
How can I fix this?
You could use javax.ws.rs.core.GenericEntity
:
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Path("/foo")
public GenericEntity<List<String>> stringlist()
{
List<String> list = Arrays.asList("test", "as");
return new GenericEntity<List<String>>(list) {};
}
Okay, I could fix it by searching the samples
This does work, but it can only be used for JSON and not for XML
@GET
@Produces({MediaType.APPLICATION_JSON})
@Path("/get")
public JSONArray get() {;
return new JSONArray(dao.getStringList());
}
Fixes problem, but is there also a generic approach?
In order to convince Jersey to output lists the way you want to, you need to provide an own ContextResolver:
@Provider
public class JaxbContentResolver implements ContextResolver<JAXBContext> {
private Log log = LogFactory.getLog(getClass());
private JAXBContext context;
public JaxbContentResolver() throws JAXBException {
Class[] types = {StringList.class};
context = new JSONJAXBContext(JSONConfiguration.mapped().rootUnwrapping(true).arrays("data").build(), types);
}
@Override
public JAXBContext getContext(Class<?> objectType) {
log.trace("Entering Test-getContext for type: " + objectType.getSimpleName());
return context;
}
}
This way lists will appear the way you want them to.
NOTE: One downside of this approach is that you need to maintain one additional spot in your code; in case you want to add another (list wrapper) class to your REST interface, you need to remember to go to above code and add that class in your ContextResolver...
You need to use the alternative JSON configuration JSONConfiguration.natural()
.
Best you create your own ContextResolver
using that alternative configuration as a provider and tell it which classes it is responsible for.
I am unaware of a method to use the alternative configuration globally in another way.
In addition to the provided answers, if you still get MessageBodyWriter not found
, try adding a dependency such as:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
</dependency>
My solution to this was a wrapper class (found it somewhere). It works now. I don't understand the thoughts behind not supporting javas List class as a root element. Perhaps it has to do with some json specification / best practice i'm not aware of.
But for now im using this:
public class Houses {
private List<String> houses;
// Needed for jersey
public Houses() { }
public Houses(List<String> houses) {
this.houses = houses;
}
public void setHouses(List<String> houses) {
this.houses = houses;
}
public List<String> getHouses() {
return this.houses;
}
}