Applying different Jackson filter for different Jersey REST service calls

jackson, java, jax-rs, jersey

Solution

I was in the same situation, after tons of research, I figured it out, the solution is to use `@JsonView` and Spring which can inject an `ObjectMapper` into the JSON Writer without killing the beauty of Jersey.

I am working on a set of REST APIs, I want to get a list of instances of `SystemObject` and the detail a specific instance of `SystemObject`, just like you I just want very limited of number of properties of each instance in the list and some additional properties in the detail, I just define Views for them, and add annotation in the `SystemObject` class. but by default, all properties with no `@JsonView` annotation will be output to the JSON, but there is a configuration `item(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION)` I can use to exclude them.

The problem is that I have to set it to true to meet my need. but I can not change the ObjectMapper which does the magic to convert the object to JSON, by reading the 3 articles below, I got the idea that the only way I can do is to inject a Modified `ObjectMapper` to Jersey. Now I got what I want.

It is like you create multiple views against a database table.

These 3 links will help you in different ways:

How to create a ObjectMapperProvider which can be used by Spring to inject

Jersey, Jackson, Spring and JSON

Jersey + Spring integration example

REST resource:

package com.john.rest.resource;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.UriInfo;

import org.codehaus.jackson.map.annotate.JsonView;
import org.springframework.stereotype.Component;

import com.midtronics.esp.common.EspException;
import com.midtronics.esp.common.SystemObject;
import com.midtronics.esp.mobile.model.SystemObjectView;
import com.midtronics.esp.model.accesscontrol.AccessControlBean;
import com.midtronics.esp.model.site.SiteBean;

@Component
@Path("/hierarchy")
public class Hierarchy {

    // Allows to insert contextual objects into the class, 
    // e.g. ServletContext, Request, Response, UriInfo
    @Context
    UriInfo uriInfo;

    @Context
    Request request;

    // Return the list of sites
    @GET
    @Path("sites")
    @Produces(MediaType.APPLICATION_JSON)
    @JsonView({SystemObjectView.ObjectList.class})
    public List<SystemObject> listSite(
            @HeaderParam("userId") String userId, 
            @HeaderParam("password") String password) {
        ArrayList<SystemObject> sites= new ArrayList<SystemObject>();

        try{
            if(!AccessControlBean.CheckUser(userId, password)){
                throw new WebApplicationException(401);
            }
            SystemObject.GetSiteListByPage(sites, 2, 3);

            return sites;
        } catch(EspException e){
            throw new WebApplicationException(401);
        } catch (Exception e) {
            throw new WebApplicationException(500);
        }
    }

    // Return the number of sites
    @GET
    @Path("sites/total")
    @Produces(MediaType.TEXT_PLAIN)
    public String getSiteNumber(@HeaderParam("userId") String userId, 
            @HeaderParam("password") String password) {
        try{
            return Integer.toString(SiteBean.GetSiteTotal()); 
        } catch(EspException e){
            throw new WebApplicationException(401);
        } catch (Exception e) {
            throw new WebApplicationException(500);
        }
    }

}

REST model:

package com.john.rest.model;

import java.io.Serializable;
import java.util.ArrayList;

import javax.xml.bind.annotation.XmlRootElement;

import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.annotate.JsonView;

import com.midtronics.esp.mobile.model.SystemObjectView;
import com.midtronics.esp.model.common.ICommonDAO;

@XmlRootElement
public class SystemObject implements Serializable
{
    private static final long serialVersionUID = 3989499187492868996L;

    @JsonProperty("id")
    @JsonView({SystemObjectView.ObjectList.class, SystemObjectView.ObjectDetail.class})
    protected String objectID = "";

    @JsonProperty("parentId")
    protected String parentID = "";

    @JsonProperty("name")
    @JsonView({SystemObjectView.ObjectList.class, SystemObjectView.ObjectDetail.class})
    protected String objectName = "";

    //getters...
    //setters...

}

REST model view:

package com.john.rest.model;

public class SystemObjectView {
    public static class ObjectList { };

    public static class ObjectDetail extends ObjectList { }
}

Problem

I am using Jersey to implement JAX-RS REST-style services along with Jackson 2.0.2 for the JSON mapping. One of these REST services returns a `List<EntityA>` (let's call it `indexA`) where `EntityA` contains another `List<EntityB>` whereas another service just returns a `List<EntityB>` (let's call it `indexB`): ``` @Entity @JsonAutoDetect public class EntityA { @Id private String id; @OneToMany private List<EntityB> b; ... } @Entity @JsonAutoDetect @JsonFilter("bFilter") public class EntityB { @Id private String id; private String some; private String other; private String attributes; ... } @Path("/a") public class AResource { @GET @Path("/") public List<EntityA> indexA() { ... } } @Path("/b") public class BResource { @GET @Path("/") public List<EntityB> indexB() { ... } } ``` What I'd like to achieve is to apply a Jackson filter to the `indexA` invocation so that not all attributes of the child `EntityB` elements are serialized. OTOH, `indexB` should return `EntityB` in its completeness. I am aware of the existence of a `ContextResolver<ObjectMapper>`, which I am already using for other purposes. Unfortunately, for the `ContextResolver` it seems to be impossible to distinguish both service invocations as the `Class` supplied to `ContextResolver.getContext(Class)` is `ArrayList` in both cases (and thanks to type erasure I cannot figure out the generic type parameters). Are there any hooks better suited at configuring an `ObjectMapper`/`FilterProvider` depending on the entity type that is being mapped? I could use the approach proposed in How to return a partial JSON response using Java?: Manually mapping to a `String`, but that kills the whole beauty of a declarative annotation-based approach, so I'd like to avoid this.

Original source

Related problems