JSON in Query Params or How to Inject Custom Java Types via JAX-RS Parameter Annotations

Although I am not a big fan of sending JSON in other places than in the message body as the entity, for example in query parameter in case of requests, it’s not a rare use-case and the way how it can be solved in JAX-RS 2.0 gives me a nice opportunity to illustrate usage of two new interfaces, ParamConverterProvider and ParamConverter. You can then re-use this approach to inject other media-types or formats your application relies on via @*Param annotations (@MatrixParam, @QueryParam, @PathParam, @CookieParam, @HeaderParam).

Use-Case: Sending JSON as Query Parameter

All snippets listed in this article are taken from jersey-param-converters (see Example) available at GitHub.

Java POJO <-> JSON

In the examples below I’ll be using the following Entity class as an input and also as output in my resource methods:

public class Entity {

    private String foo;
    private String bar;

    // ...

}

In JSON format an instance of this class would, more-or-less, look like:

{"Entity":{"foo":"foo","bar":"bar"}}

or

{
  "foo" : "foo",
  "bar" : "bar"
}

Client

Let’s assume that you want to send a JSON string as a query or a header parameter to your REST service. The client code, in case of query parameter, could look like this:

final Response response = ClientBuilder.newClient()
        .target("json-param-converter")
        .path("query")
        .queryParam("entity",
            UriComponent.encode(json, UriComponent.Type.QUERY_PARAM_SPACE_ENCODED))
        .request()
        .get();

To make sure the resulting JSON is properly encoded in URI I am using UriComponent helper class from Jersey to encode JSON as QUERY_PARAM_SPACE_ENCODED type.

The actual request would look like:

Feb 09, 2014 4:55:44 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 1 * Sending client request on thread main
1 > GET http://localhost:9998/json-param-converter/query?entity=%7B%22Entity%22:%7B%22foo%22:%22foo%22,%22bar%22:%22bar%22%7D%7D

In case we want to send our JSON as a header value the client code is little bit simpler:

final Response response = ClientBuilder.newClient()
        .target("json-param-converter")
        .path("header")
        .request()
        .header("Entity", json)
        .get();

And the request:

Feb 09, 2014 4:55:44 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 1 * Sending client request on thread main
1 > GET http://localhost:9998/json-param-converter/header
1 > Entity: {"Entity":{"foo":"foo","bar":"bar"}}

Server

We want the resource class, handling requests described above, to be as simple as possible. No custom conversion of JSON strings to Java objects in our resource methods. JAX-RS runtime should do this for us. So, all we need to do is write a resource class similar to:

@Path("json-param-converter")
@Produces("application/json")
public class JsonParamConverterResource {

    @GET
    @Path("query")
    public Entity getViaQueryParam(
            @QueryParam("entity")
            @DefaultValue("{\"Entity\":{\"foo\":\"bar\",\"bar\":\"foo\"}}")
            final Entity entity) {
        return entity;
    }

    @GET
    @Path("header")
    public Entity getViaHeaderParam(@HeaderParam("Entity") final Entity entity) {
        return entity;
    }
}

As you can see there are two @GET methods. Both are returning injected entity, transformed to JSON, to the client. The first one is trying to inject value of a query parameter named entity (see @QueryParam) into the method parameter of type Entity. If JAX-RS runtime cannot find such a query parameter the value from @DefaultValue is used to create Entity instead. The second one is, similarly, injecting value of a request header named Entity (see @HeaderParam).

From both of the resource methods we’d like to return a response similar to:

Feb 09, 2014 4:55:44 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 2 * Client response received on thread main
2 < 200
2 < Content-Length: 36
2 < Content-Type: application/json
2 < Date: Fri, 09 Feb 2014 15:55:44 GMT
{
  "foo" : "foo",
  "bar" : "bar"
}

OK, enough of what we want to achieve, let’s take a look at how to actually do it.

Param Converters (and Providers)

To make the injection work we need to create implementations of two interfaces from JAX-RS 2.0, ParamConverterProvider and ParamConverter. With the first one we simply tell the runtime whether we’re able to convert String to a particular type and with the second one we’re doing the actual conversion.

So, for our use-case of converting JSON into custom Java types we’re going to create a slightly more powerful version of converter provider and converter itself. For the purpose of converting we’ll be using Jackson library, particularly it’s ObjectMapper class. As you might know it’s possible to pass your own instances of ObjectMapper to Jackson message body providers via custom ContextResolver (see below). We want to use this mechanism even with converting JSON to parameters so both approaches (converting request entity and converting parameters) have the configuration of ObjectMapper in one place:

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {

    @Override
    public ObjectMapper getContext(final Class<?> type) {
        return new ObjectMapper()
                .configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
                .configure(SerializationFeature.INDENT_OUTPUT, true);
    }
}

And our implementations of ParamConverterProvider and ParamConverter are:

 1@Provider
 2public class JacksonJsonParamConverterProvider implements ParamConverterProvider {
 3
 4    @Context
 5    private Providers providers;
 6
 7    @Override
 8    public <T> ParamConverter<T> getConverter(final Class<T> rawType,
 9                                              final Type genericType,
10                                              final Annotation[] annotations) {
11        // Check whether we can convert the given type with Jackson.
12        final MessageBodyReader<T> mbr = providers.getMessageBodyReader(rawType,
13                genericType, annotations, MediaType.APPLICATION_JSON_TYPE);
14        if (mbr == null
15              || !mbr.isReadable(rawType, genericType, annotations, MediaType.APPLICATION_JSON_TYPE)) {
16            return null;
17        }
18
19        // Obtain custom ObjectMapper for special handling.
20        final ContextResolver<ObjectMapper> contextResolver = providers
21                .getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE);
22
23        final ObjectMapper mapper = contextResolver != null ?
24                contextResolver.getContext(rawType) : new ObjectMapper();
25
26        // Create ParamConverter.
27        return new ParamConverter<T>() {
28
29            @Override
30            public T fromString(final String value) {
31                try {
32                    return mapper.reader(rawType).readValue(value);
33                } catch (IOException e) {
34                    throw new ProcessingException(e);
35                }
36            }
37
38            @Override
39            public String toString(final T value) {
40                try {
41                    return mapper.writer().writeValueAsString(value);
42                } catch (JsonProcessingException e) {
43                    throw new ProcessingException(e);
44                }
45            }
46        };
47    }
48}

As you can see it’s not complicated at all and hence it’s pretty useful. In the first part (lines 12–17) I am making sure that the JSON<->Java message body reader (the one from Jackson as I didn’t register any other JSON providers) is capable of unmarshalling JSON into Java POJO of given type. If this is the case I know that I can also convert JSON into this Java POJO and inject it via parameter annotations.

In the second part (lines 20–24) I am looking for a custom ObjectMapper. If there is one I’ll use it during conversion otherwise I’ll create a default one.

And finally on lines 27–46 I am creating new ParamConverter which is actually converting the JSON string into an instance of given type (line 32). I could use the retrieved message body reader to the job and the result would be the same, of course. But I think my approach is better to illustrate how this mechanism really works.

Default JAX-RS Param Converters

In some (basic) cases it’s not necessary to create your own param converters (and providers) to inject parameters via @*Param annotations (@MatrixParam, @QueryParam, @PathParam, @CookieParam, @HeaderParam) because JAX-RS 2.0 implementations have to support the following types:

  1. Primitive types.
  2. Types that have a constructor that accepts a single String argument.
  3. Types that have a static method named valueOf or fromString with a single String argument that return an instance of the type. If both methods are present then valueOf MUST be used unless the type is an enum in which case fromString MUST be used.
  4. List, Set, or SortedSet, where T satisfies 2 or 3 above.

This basically tells us that when there are only a few classes which we want to inject it’s sufficient to create static methods fromString / valueOf in these classes (or make sure the classes have a constructor which accepts one string argument) and we can use them as injection targets.

Additional Jersey Param Converters

In addition to converters mentioned above Jersey provides two more param converters for the following types:

  1. Date – for following date format patterns
    • EEE, dd MMM yyyy HH:mm:ss zzz (RFC 1123)
    • EEEE, dd-MMM-yy HH:mm:ss zzz (RFC 1036)
    • EEE MMM d HH:mm:ss yyyy (ANSI C)
  2. JAXB bean – has to be annotated either with @XmlRootElement or @XmlType

The Date param converter has the highest priority and the JAXB one the lowest priority from all the default converters. This means than in case you have a JAXB class like:

@XmlRootElement
public class JaxbBean {

    private String value;

    public static JaxbBean fromString(final String bean) {
        // ...
    }

    // getters and setters
}

The injected JaxbBean would be created using fromString JAX-RS default param converter and not the JAXB one.

Example

As I mentioned earlier there is an example you can try (both client and server sides) available on GitHub: jersey-param-converters. It’s a simple JAX-RS web-application with 3 test-cases that are representing the client side story.

Further Reading