29

I've been asked to beautify default Jackson JSON coming out of a RestEasy endpoint. I did some research on Jackson and wrote some standalone code to be able to suppress nulls, customize data formats etc. Now the challenge is injecting this code in RestEasy's JSON serialization.

Judging from the forum posts this is trivial in Spring, however doesn't seem to be the case in RestEasy. I wrote a ContextResolver and configured as resteasy.provider in context params in web.xml (on Tomcat) but that prevents the webapp from loading on Tomcat.

Now I'm trying to extend javax.ws.rs.core.Application and provide a ContextResolver but making no progress. Is this straight forward, has anyone done this? Any help is greatly appreciated.

Daniel Serodio
  • 4,229
  • 5
  • 37
  • 33
codegeek
  • 551
  • 1
  • 4
  • 7
  • Ok, I was able to do this by writing a custom JacksonJsonProvider based on the wiki.fasterxml.com/JacksonFAQJaxRs entry.The code is as follows - – codegeek Dec 17 '11 at 00:21

6 Answers6

17

I found a nicer way of modifying the Jackson SerializationConfig - you can intercept the ObjectMapper creation by using a JAX-RS ContextResolver.

@Provider
@Produces(Array(MediaType.APPLICATION_JSON))
class JacksonConfig extends ContextResolver[ObjectMapper] {

  val mapper = new ObjectMapper()
  mapper.getSerializationConfig.setSerializationInclusion(Inclusion.NON_NULL)

  def getContext(objectType: Class[_]) = mapper
}

You will need to register with RESTEasy in one of the following ways:

  • Return it as a class or instance from a javax.ws.rs.core.Application implementation
  • List it as a provider with resteasy.providers
  • Let RESTEasy automatically scan for it within your WAR file. See Configuration Guide
  • Manually add it via ResteasyProviderFactory.getInstance().registerProvider(Class) or registerProviderInstance(Object)

Reference: RESTEasy docs

Reference: Nicklas Karlsson on the JBoss forums

Please note that this works with RESTEasy 2.3.2 which ships as a module in JBoss 7.1.1.Final, but does not appear to work with RESTEasy 3.0-beta5.

James Baxter
  • 1,237
  • 9
  • 17
15

Ok,I figured it out, I was able to do this by writing a custom JacksonJsonProvider based on the Jackson FAQ: JAX-RS.The code is as follows:

@Provider
public class QBOJacksonJsonProvider extends JacksonJsonProvider {
    public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    @Override
    public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String,Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
        Log.info(getClass(), "In custom JSON provider");
        //get the Object Mapper
        ObjectMapper mapper = locateMapper(type, mediaType);
        // Suppress null properties in JSON output
        mapper.getSerializationConfig().setSerializationInclusion(org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_NULL);
        // Set human readable date format
        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
        mapper.getSerializationConfig().setDateFormat(sdf);

        super.writeTo(value, type, genericType, annotations, mediaType, httpHeaders, entityStream);
    }
}
Daniel Serodio
  • 4,229
  • 5
  • 37
  • 33
codegeek
  • 551
  • 1
  • 4
  • 7
  • 11
    How did you "register" this Provider? – Daniel Serodio May 02 '13 at 21:47
  • 1
    Since Jackson 1.8 setDateFormat() is deprecated use mapper.getSerializationConfig().withDateFormat() –  May 13 '13 at 13:51
  • 6
    @DanielSerodio can be done in web.xml ` resteasy.providers com.package.JsonProvider ` – thiagoh Aug 12 '14 at 17:07
  • This was the only way to override a custom `ObjectMapper` drawn in from an external library and make sure my own ObjectMapper is used (by overriding `locateMapper` method to return my ObjectMapper). – Gregor Jun 30 '17 at 09:43
6

If you're using the Jackson2 provider you need to do something slightly different from the previous answer. This example will pretty-print the output by default

@Provider
public class JSONProvider extends ResteasyJackson2Provider {

  @Override
  public void writeTo(Object value, Class<?> type, Type genericType, Annotation[]  annotations, MediaType json, MultivaluedMap<String, Object> headers, OutputStream body) throws IOException {

    ObjectMapper mapper = locateMapper(type, json);
    mapper.enable(SerializationFeature.INDENT_OUTPUT);

    super.writeTo(value, type, genericType, annotations, json, headers, body);
  }

}

and to register it in your web-xml, if you don't have autoregister on, add it to your resteasy.providers context-param

Tadhg
  • 561
  • 5
  • 15
  • 3
    But this way the mapper configuration will be changed on every serialization. How to configure mapper one time? For example if I need to register a Jackson Module. I don't want to re-register it on every http request. – Ruslan Stelmachenko May 16 '14 at 04:49
3

I had a lot of work to register it in my application even following the tip to use context-param once I'm using spring boot and have not web.xml, here what I did

Custom provider

package com.mageddo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;

@Provider
@Consumes({"application/json", "application/*+json", "text/json"})
@Produces({"application/json", "application/*+json", "text/json"})
public class CustomJacksonJsonProvider extends ResteasyJackson2Provider {

    private ObjectMapper objectMapper = new ObjectMapper(); // my own object mapper

    @Override
    public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
        return objectMapper;
    }
}

Registering on application

META-INF/services/javax.ws.rs.ext.Providers

com.mageddo.CustomJacksonJsonProvider
org.jboss.resteasy.plugins.providers.jackson.UnrecognizedPropertyExceptionHandler
deFreitas
  • 4,196
  • 2
  • 33
  • 43
2

Provider for Jackson ObjectMapper should be standard JAX-RS way of doing this (works with Jersey), so it seems like the way to go with RESTeasy as well.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • Thanks for your reply, could you elaborate on this? If you can let me know how your are registering your custom objectmapper/serializer/contextresolver etc, it would be great. – codegeek Dec 15 '11 at 21:16
  • 1
    Ok, thanks I figured it out.. I was battling with unrelated issues, this is straight forward, I wrote a custom JacksonJsonProvider as follows - – codegeek Dec 17 '11 at 00:16
0

If you are using the Jackson 2.2.x provider, Resteasy has provided a pretty-printing annotation simliar with the one in JAXB provider:

org.jboss.resteasy.annotations.providers.jackson.Formatted

Here is an example:

@GET
@Produces("application/json")
@Path("/formatted/{id}")
@Formatted
public Product getFormattedProduct()
{
    return new Product(333, "robot");
}

As the example shown above, the @Formatted annotation will enable the underlying Jackson option "SerializationFeature.INDENT_OUTPUT".

© RESTEasy User Guide.

This is not a global solution, but you can put the annotation on classes too.

Vsevolod Golovanov
  • 4,068
  • 3
  • 31
  • 65