JBoss.orgCommunity Documentation

Chapter 23. JSON Support via Jackson

23.1. Using Jackson 1.9.x Outside of WildFly
23.2. Using Jackson 1.9.x Inside WildFly 8
23.3. Using Jackson 2 Outside of WildFly
23.4. Using Jackson 2 Inside WildFly 9 and above
23.5. Additional RESTEasy Specifics
23.6. Possible Conflict With JAXB Provider
23.7. JSONP Support
23.8. Jackson JSON Decorator
23.9. JSON Filter Support

Besides the Jettision JAXB adapter for JSON, RESTEasy also supports integration with the Jackson project. Many users find the output from Jackson much nicer than the Badger format or Mapped format provided by Jettison. For more on Jackson 2, see http://wiki.fasterxml.com/JacksonHome. Besides JAXB like APIs, it has a JavaBean based model, described at http://wiki.fasterxml.com/JacksonDataBinding, which allows you to easily marshal Java objects to and from JSON. RESTEasy integrates with the JavaBean model. While Jackson does come with its own JAX-RS integration, RESTEasy expanded it a little, as decribed below.

NOTE. The resteasy-jackson-provider module, which is based on the outdated Jackson 1.9.x, is currently deprecated, and will be removed in a release subsequent to 3.1.0.Final. The resteasy-jackson2-provider module is based on Jackson 2.

If you're deploying RESTEasy outside of WildFly, add the RESTEasy Jackson provder to your WAR pom.xml build:

<dependency>
   <groupId>org.jboss.resteasy</groupId>
   <artifactId>resteasy-jackson-provider</artifactId>
   <version>${version.resteasy}</version>
</dependency>

If you're deploying RESTEasy with WildFly 8, there's nothing you need to do except to make sure you've updated your installation with the latest and greatest RESTEasy. See the Installation/Configuration section of this documentation for more details.

If you're deploying RESTEasy outside of WildFly, add the RESTEasy Jackson provder to your WAR pom.xml build:

<dependency>
   <groupId>org.jboss.resteasy</groupId>
   <artifactId>resteasy-jackson2-provider</artifactId>
   <version>${version.resteasy}</version>
</dependency>

If you're deploying RESTEasy with WildFly 9 or above, there's nothing you need to do except to make sure you've updated your installation with the latest and greatest RESTEasy. See the Installation/Configuration section of this documentation for more details.

The first extra piece that RESTEasy added to the integration was to support "application/*+json". Jackson would only accept "application/json" and "text/json" as valid media types. This allows you to create json-based media types and still let Jackson marshal things for you. For example:

@Path("/customers")
public class MyService {

    @GET
    @Produces("application/vnd.customer+json")
    public Customer[] getCustomers() {}
}

Another problem that occurs is when you are using the RESTEasy JAXB providers alongside Jackson. You may want to use Jettison and JAXB to output your JSON instead of Jackson. In this case, you must either not install the Jackson provider, or use the annotation @org.jboss.resteasy.annotations.providers.NoJackson on your JAXB annotated classes. For example:

@XmlRootElement
@NoJackson
public class Customer {...}

@Path("/customers")
public class MyService {

    @GET
    @Produces("application/vnd.customer+json")
    public Customer[] getCustomers() {}
}

If you can't annotate the JAXB class with @NoJackson, then you can use the annotation on a method parameter. For example:

@XmlRootElement
public class Customer {...}

@Path("/customers")
public class MyService {

    @GET
    @Produces("application/vnd.customer+json")
    @NoJackson
    public Customer[] getCustomers() {}

    @POST
    @Consumes("application/vnd.customer+json")
    public void createCustomer(@NoJackson Customer[] customers) {...}
}

If your Jackson classes are annotated with JAXB annotations and you have the resteasy-jaxb-provider in your classpath, you may trigger the Jettision JAXB marshalling code. To turn off the JAXB json marshaller use the @org.jboss.resteasy.annotations.providers.jaxb.IgnoreMediaTypes("application/*+json") on your classes.

If you're using Jackson, RESTEasy has JSONP that you can turn on by adding the provider org.jboss.resteasy.plugins.providers.jackson.JacksonJsonpInterceptor (Jackson2JsonpInterceptor if you're using the Jackson2 provider) to your deployments. If the media type of the response is json and a callback query parameter is given, the response will be a javascript snippet with a method call of the method defined by the callback parameter. For example:

GET /resources/stuff?callback=processStuffResponse

will produce this response:

processStuffResponse(<nomal JSON body>)

This supports the default behavior of jQuery. To enable JacksonJsonpInterceptor in WildFly, you need to import annotations from org.jboss.resteasy.resteasy-jackson-provider module using jboss-deployment-structure.xml:

<jboss-deployment-structure>
  <deployment>
    <dependencies>
      <module name="org.jboss.resteasy.resteasy-jackson-provider" annotations="true"/>
    </dependencies>
  </deployment>
</jboss-deployment-structure>

You can change the name of the callback parameter by setting the callbackQueryParameter property.

JacksonJsonpInterceptor can wrap the response into a try-catch block:

try{processStuffResponse(<normal JSON body>)}catch(e){}

You can enable this feature by setting the resteasy.jsonp.silent property to true

Note. Because JSONP can be used in Cross Site Scripting Inclusion (XSSI) attacks, Jackson2JsonpInterceptor is disabled by default. Two steps are necessary to enable it:

  1. As noted above, Jackson2JsonpInterceptor must be included in the deployment. For example, a service file META-INF/services/javax.ws.rs.ext.Providers with the line
    org.jboss.resteasy.plugins.providers.jackson.Jackson2JsonpInterceptor
    
    may be included on the classpath
  2. Also, the servlet context parameter parameter "resteasy.jsonp.enable" must be set to "true".

If you are using the Jackson 2 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".

In Jackson2 , there is new feature JsonFilter to allow annotate class with @JsonFilter and doing dynamic filtering. Here is an example which defines mapping from "nameFilter" to filter instances and filter bean properties when serilize to json format:

@JsonFilter(value="nameFilter")
public class Jackson2Product {
    protected String name;
    protected int id;
    public Jackson2Product() {
    }
    public Jackson2Product(final int id, final String name) {
        this.id = id;
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
}

@JsonFilter annotates resource class to filter out some property not to serialize in the json response. To map the filter id and instance we need to create another jackson class to add the id and filter instance map:


public class ObjectFilterModifier extends ObjectWriterModifier {
	public ObjectFilterModifier() {
	}
	@Override
	public ObjectWriter modify(EndpointConfigBase<?> endpoint,
			MultivaluedMap<String, Object> httpHeaders, Object valueToWrite,
			ObjectWriter w, JsonGenerator jg) throws IOException {

		FilterProvider filterProvider = new SimpleFilterProvider().addFilter(
				"nameFilter",
				SimpleBeanPropertyFilter.filterOutAllExcept("name"));
		return w.with(filterProvider);

	}
}

Here the method modify() will take care of filtering all properties except "name" property before write. To make this work, we need let RESTEasy know this mapping info. This can be easily set in a WriterInterceptor using Jackson's ObjectWriterInjector:


@Provider
public class JsonFilterWriteInterceptor implements WriterInterceptor{

	private ObjectFilterModifier modifier = new ObjectFilterModifier();
	@Override
	public void aroundWriteTo(WriterInterceptorContext context)
			throws IOException, WebApplicationException {
		//set a threadlocal modifier
	    ObjectWriterInjector.set(modifier);	
		context.proceed();	
	}

}

Alternatively, Jackson's documentation suggest doing the same in a servlet filter; that however potentially leads to issues on RESTEasy, as the ObjectFilterModifier ends up being stored using a ThreadLocal object and there's no guarantee the same thread serving the servlet filter will be running the resource endpoint execution too. So, for the servlet filter scenario, RESTEasy offers its own injector that relies on the current thread context classloader for carrying over the specified modifier:


public class ObjectWriterModifierFilter implements Filter {
	private static ObjectFilterModifier modifier = new ObjectFilterModifier();

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		ResteasyObjectWriterInjector.set(Thread.currentThread().getContextClassLoader(), modifier);
		chain.doFilter(request, response);
	}

	@Override
	public void destroy() {
	}

}