In my application I use Jersey REST to serialize complex objects. This works quite fine. But there are a few method which simply return an int or boolean.
Jersey can
I had the same problem today and didn't give up until i found a really good suitable solution. I can not update the jersey library from 1.1.5 it is a Legacy System. My Rest Service returns a List and they should follow those rules.
Start from easy to impossible.
3) nothing today normal JSON Mapping
2) Register JAXBContextResolver like the following
@Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private final JAXBContext context;
private final Set<Class<?>> types;
private Class<?>[] ctypes = { Pojo.class }; //your pojo class
public JAXBContextResolver() throws Exception {
this.types = new HashSet<Class<?>>(Arrays.asList(ctypes));
this.context = new JSONJAXBContext(JSONConfiguration.mapped()
.rootUnwrapping(true)
.arrays("propertyName") //that should rendered as JSONArray even if the List only contain one element but doesn't handle the empty Collection case
.build()
, ctypes);
}
@Override
public JAXBContext getContext(Class<?> objectType) {
return (types.contains(objectType)) ? context : null;
}
}
1) The following approach only works for Collections$EmptyList class. May you find a way to make it general for all Collections they are empty. May code deal with EmptyList so.
@Provider
@Produces(value={MediaType.APPLICATION_JSON})
public class EmptyListWriter implements MessageBodyWriter<AbstractList> {
private static final String EMPTY_JSON_ARRAY = "[]";
@Override
public long getSize(AbstractList list, Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return EMPTY_JSON_ARRAY.length();
}
@Override
public boolean isWriteable(Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return clazz.getName().equals("java.util.Collections$EmptyList");
}
@Override
public void writeTo(AbstractList list, Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> headers, OutputStream outputStream) throws IOException, WebApplicationException {
if (list.isEmpty())
outputStream.write(EMPTY_JSON_ARRAY.getBytes());
}
}
Tell Jersey generate proper JSON documents (natural json). I use same class for rest app and JAXBContext resolver, found it the most clean encapsulation.
Better programmer could implement helper to iterate .class files and list appropriate classes automatically by identifying @Annotation tags. I don't know how to do it runtime in an own source code.
These two links were helpful studying this extra java jargon. I don't know why there is no Jersey parameter to make all just work out of the box.
WEB-INF/web.xml (snippet):
<servlet>
<servlet-name>RESTServlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.myapp.rest.RESTApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>RESTServlet</servlet-name>
<url-pattern>/servlet/rest/*</url-pattern>
</servlet-mapping>
com.myapp.rest.RESTApplication.java
package com.myapp.rest;
import java.util.*;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.ContextResolver;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;
public class RESTApplication extends Application implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types;
public RESTApplication() throws JAXBException {
// list JAXB bean types to be used for REST serialization
types = new Class[] {
com.myapp.rest.MyBean1.class,
com.myapp.rest.MyBean2.class,
};
context = new JSONJAXBContext(JSONConfiguration.natural().build(), types);
}
@Override
public Set<Class<?>> getClasses() {
// list JAXB resource/provider/resolver classes
Set<Class<?>> classes = new HashSet<Class<?>>();
//for(Class<?> type : types)
// classes.add(type);
classes.add(MyBeansResource.class);
classes.add(this.getClass()); // used as a ContextResolver class
return classes;
}
@Override
public JAXBContext getContext(Class<?> objectType) {
// this is called each time when rest path was called by remote client
for (Class<?> type : types) {
if (type==objectType)
return context;
}
return null;
}
}
Classes MyBean1,MyBean2 are plain java objects and MyBeansResource class is the one with @Path rest functions. There is nothing special in them expect standard jaxp @Annotations here and there. After this java jargon JSON documents have
I use the following environment
jersey-archive.zip had older asm-3.1.jar file, probably works fine but chapter_deps.html links to a newer file. See link list at the top.
Edit I found an excellent(fast, lightweight just 15KB) annotation discovery tool. See this post about how I autodiscover types at runtime and no longer need to edit RESTApplication each time new java(jaxb) bean is added.
https://github.com/rmuller/infomas-asl/issues/7
Have a look at Genson.It helped me a lot with a similar problem.With Genson you could use generics like int,boolean, lists and so on...Here is a quick example.
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getMagicList() {
List<Object> objList = new ArrayList<>();
stringList.add("Random String");
stringList.add(121); //int
stringList.add(1.22); //double
stringList.add(false); //bolean
return Response.status(Status.OK).entity(objList).build();
}
This will produce a valid JSON witch can be retrieved very simple like this:
Client client = Client.create();
WebResource webResource = client.resource("...path to resource...");
List objList = webResource.accept(MediaType.APPLICATION_JSON).get(ArrayList.class);
for (Object obj : objList) {
System.out.println(obj.getClass());
}
You will see that Genson will help you decode the JSON on the client side also and output the correct class for each.
I've just discovered that returning a primitive type with Jersey is problematic. I've decided to return String instead. Maybe this is not clean, but I don't think it's too dirty. The Java client, which is written by the same author of the server most of the times, can wrap such a string return value and convert it back to int. Clients written in other languages must be aware of return types any way.
Defining RestInteger, RestBoolean may be another option, however it's more cumbersome and I see too little advantage in it to be attractive.
Or maybe am I missing something important here?
Actually your best bet is to write a custom ContextResolver Provider like the following that uses natural building of JSON.
@Provider
public class YourContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types = { YourSpecialBean.class };
public YourContextResolver() throws Exception {
this.context = new JSONJAXBContext(
JSONConfiguration.natural().build(), types);
}
public JAXBContext getContext(Class<?> objectType) {
for (int i = 0; i < this.types.length; i++)
if (this.types[i].equals(objectType)) return context;
return null;
}
}
The only thing special here to notice is the YourSpecialBean.class in the Class[]. This defines an array of class types that this provider will resolve naturally.
Are you writing a service or a client? In the service-end of things, you would simply write a MessageBodyWriter to serialize a stream of data to a Java object for your types. In my use cases, the services I'm writing output to JSON or XML, and in XML's case, I just throw one JAXB annotation on the top of my classes and I'm done.
Have you looked at the Jersey User guide regarding this?
3.6. Adding support for new representations