43

I have successfully set up a quick test of creating a "REST-like" service that returns an object serialized to JSON, and that was quite easy and quick (based on this article).

But while returning JSON-ified objects was easy as peach, I have yet to see any examples dealing with input parameters that are not primitives. How can I pass in a complex object as an argument? I am using Apache CXF, but examples using other frameworks like Jackson are welcome too :)

Client side would probably be something like building a javascript object, pass it into JSON.stringify(complexObj), and pass that string as one of the parameters.

The service would probably look something like this

@Service("myService")
class RestService {
    @GET
    @Produces("application/json")
    @Path("/fooBar")
    public Result fooBar(@QueryParam("foo") double foo, @QueryParam("bar") double bar,
        @QueryParam("object") MyComplex object) throws WebServiceException {
    ...
    }
}

Sending serialized objects as parameters would probably quickly touch the 2KB URL-limit imposed by Internet Explorer. Would you recommend using POST in these cases, and would I need to change much in the function definitions?

oligofren
  • 20,744
  • 16
  • 93
  • 180

3 Answers3

32

After digging a bit I quickly found out there are basically two options:

Option 1

You pass a "wrapper object" containing all the other parameters to the service. You might need to annotate this wrapper class with JAXB annotations like @XmlRootElement in order for this to work with the Jettison based provider, but if you use Jackson in stead there is no need. Just set the content type to the right type and the right message body reader will be invoked. This will only work for POST type services of course (AFAIK).

Example

This is just an example of turning the service mentioned in the original question into one using a wrapper object.

@Service("myService")
class RestService {

    @POST
    @Produces("application/json")
    @Path("/fooBar")
    public Result fooBar(

          /** 
          * Using "" will inject all form params directly into a ParamsWrapper 
          * @see http://cxf.apache.org/docs/jax-rs-basics.html
          */
          @FormParam("") FooBarParamsWrapper wrapper

        ) throws WebServiceException {
            doSomething(wrapper.foo);
    }
}

class ParamsWrapper {
  double foo, bar;
  MyComplexObject object;
}

Option 2

You can provide some special string format that you pack your objects into and then implement either a constructor taking a string, a static valueOf(String s) or a static fromString(String s) in the class that will take this string and create an object from it. Or quite similar, create a ParameterHandler that does exactly the same.

AFAIK, only the second version will allow you to call your services from a browser using JSONP (since JSONP is a trick restricted to GET). I chose this route to be able to pass arrays of complex objects in the URI.

As an example of how this works, take the following domain class and service

Example

@GET
@Path("myService")
public void myService(@QueryParam("a") MyClass [] myVals) {
    //do something
}

class MyClass {
    public int foo;
    public int bar;

   /** Deserializes an Object of class MyClass from its JSON representation */
   public static MyClass fromString(String jsonRepresentation) {
           ObjectMapper mapper = new ObjectMapper(); //Jackson's JSON marshaller
           MyClass o= null;
           try {
                   o = mapper.readValue(jsonRepresentation, MyClass.class );
           } catch (IOException e) {
                    throw new WebApplicationException()
           }
           return o;
   }
}

A URI http://my-server.com/myService?a={"foo":1, "bar":2}&a={"foo":100, "bar":200} would in this case be deserialized into an array composed of two MyClass objects.

2019 comment: Seeing that this answer still gets some hits in 2019, I feel I should comment. In hindsight, I would not recomment option 2, as going through these steps just to be able to be able to do GET calls adds complexity that's probably not worth it. If your service takes such complex input, you will probably not be able to utilize client side caching anyway, due to the number of permutations of your input. I'd just go for configuring proper Cross-Origin-Sharing (CORS) headers on the server and POST the input. Then focus on caching whatever you can on the server.

oligofren
  • 20,744
  • 16
  • 93
  • 180
  • Can you also state how the client will look like for this? Do we have serialize ourselves on the client side? – chan Mar 24 '13 at 14:37
  • In our case the clients were javascript applications. So they simply created objects like var myObject = { foo : 1, bar : 2 } which they stringified using JSON.stringify(myObject). – oligofren Mar 24 '13 at 22:28
  • So yes, by doing #2 you will probably need to do the serialization yourself. #1 is supported by CXF, so you can make it create client side stubs AFAIK. – oligofren Mar 24 '13 at 22:31
  • @oligfren Do you have any example for the first option? i.e passing a wrapper object. – thisisananth Jun 28 '13 at 13:48
  • For option 2, when there is a problem parsing MyClass object from String, how to send the WebApplicationException back to the client? "throw" does not send the exception to the client. – yo_haha Apr 13 '15 at 12:36
  • @yo_haha not sure what you mean there by "sending an exception". I no longer develop in Java, but I am pretty sure a thrown Exception will result in a HTTP 500 Server Exception - with or without stacktrace. At the very least a log dump. If you want to explicitly control the response object you can do that using the Response object in jax.rs, but that involves some refactoring. – oligofren Apr 13 '15 at 12:52
  • 1
    @yo_haha This is really a separate question you are asking anyway. Better post it as a new question. That way it will give you far more attention. – oligofren Apr 13 '15 at 12:53
7

The accepted answer is missing @BeanParam. See https://docs.jboss.org/resteasy/docs/3.0-rc-1/javadocs/javax/ws/rs/BeanParam.html for further details. It allows you to define query params inside a wrapper object. E.g.

public class TestPOJO {

    @QueryParam("someQueryParam")
    private boolean someQueryParam;

    public boolean isSomeQueryParam() {
        return someQueryParam;
    }

    public boolean setSomeQueryParam(boolean value) {
        this.someQueryParam = value;
    }
}

... // inside the Resource class
@GET
@Path("test")
public Response getTest(@BeanParam TestPOJO testPOJO) {
    ...
}
r_ganaus
  • 71
  • 2
  • 5
  • 1
    that wasn't needed at the time I wrote that. I can't tell if my code isn't valid using a later iteration of JAX-RS seven years later. – oligofren Jun 21 '18 at 13:40
  • 3
    It works, so even if the answer was not in its time, it is useful now for other people – pedrofb Apr 12 '19 at 07:02
2

the best and simplest solution is to send your object as a json string and in server side implement a method which will decode that json and map to the specified object as per your need.. and yes it`s better to use POST.

Koustuv Ganguly
  • 897
  • 7
  • 21