How to serialize Java primitives using Jersey REST
Asked Answered
U

6

8

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't handle primitive types (to my knowledge), probably because they're no annotated and Jersey has no default annotation for them. I worked around that by creating complex types like a RestBoolean or RestInteger, which simply hold an int or boolean value and have the appropriate annotations.

Isn't there an easier way than writing these container objects?

Upkeep answered 13/4, 2010 at 12:40 Comment(1)
JAX-RS/Jersey does not support serialization of primitive types or even the wrapper types such as Integer, Boolean etc. AFAIK, the approach you have taken seems to be only way.Chervonets
P
4

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.

Pastiness answered 3/2, 2013 at 10:0 Comment(0)
O
3

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

Obregon answered 29/4, 2010 at 3:12 Comment(0)
G
2

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.

Girdle answered 20/5, 2010 at 21:21 Comment(0)
F
2

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

  • zero or single-element List arrays are always written as json array ([] field)
  • primitive integers and boolean fields are written as json primitives (without quotations)

I use the following environment

  • Sun Java JDK1.6.x
  • Apache Tomcat 6.x
  • Jersey v1.14 libraries (jersey-archive-1.14.zip)
  • webapps/myapp/WEB-INF/lib folder has asm-3.3.1.jar, jackson-core-asl.jar, jersey-client.jar, jersey-core.jar, jersey-json.jar, jersey-server.jar, jersey-servlet.jar libraries
  • add optional annotation-detector.jar if you use infomas-asl discovery tool

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

Fussy answered 27/9, 2012 at 14:49 Comment(0)
K
2

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?

Kostroma answered 7/10, 2013 at 17:9 Comment(1)
this definitely should not be so cumbersome and included in the jersey class in my opinion. I made the same conclusion Sunday at 10pm I will just deal with the darn string ha.Posy
P
2

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.

  1. Empty Lists are rendered as [] (almost impossible)
  2. One Element Lists are rendered as [] (difficult but only mapping configuration)
  3. Many Element Lists are rendered as [] (easy)

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());            
    }
}
Perambulate answered 8/1, 2016 at 19:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.