2012-06-01 14 views
13

Estoy utilizando Jersey para implementar servicios estilo REST JAX-RS junto con Jackson 2.0.2 para el mapeo JSON. Uno de estos servicios REST devuelve un List<EntityA> (vamos a llamarlo indexA), donde EntityA contiene otro List<EntityB> mientras que otro servicio sólo devuelve un List<EntityB> (vamos a llamarlo indexB):Aplicación de diferentes filtros Jackson para diferentes llamadas al servicio REST de Jersey

@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() { 
    ... 
    } 
} 

Lo que me gustaría lograr es aplicar un filtro Jackson a la invocación indexA para que no se serialicen todos los atributos de los elementos EntityB secundarios. OTOH, indexB debe devolver EntityB en su integridad.

Soy consciente de la existencia de un ContextResolver<ObjectMapper>, que ya estoy utilizando para otros fines. Desafortunadamente, para el ContextResolver parece imposible distinguir ambas invocaciones de servicio ya que el Class suministrado a ContextResolver.getContext(Class) es ArrayList en ambos casos (y gracias a la borradura de tipo no puedo descifrar los parámetros de tipo genérico).

¿Hay ganchos más adecuados para configurar un ObjectMapper/FilterProvider dependiendo del tipo de entidad que se está mapeando?

Podría utilizar el enfoque propuesto en How to return a partial JSON response using Java?: mapeo manual a String, pero eso elimina la belleza de un enfoque basado en anotaciones declarativas, por lo que me gustaría evitar esto.

Respuesta

27

Estaba en la misma situación, después de toneladas de investigación, lo descubrí, la solución es usar @JsonView y Spring que puede inyectar un ObjectMapper en el escritor JSON sin matar la belleza de Jersey.

estoy trabajando en un conjunto de APIs REST, quiero obtener una lista de instancias de SystemObject y el detalle de una instancia específica de SystemObject, al igual que sólo quiero muy limitado de número de propiedades de cada instancia en el lista y algunas propiedades adicionales en el detalle, solo defino Vistas para ellas y agrego anotaciones en la clase SystemObject. pero de forma predeterminada, todas las propiedades sin anotación @JsonView se enviarán a JSON, pero hay una configuración item(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION) que puedo usar para excluirlas.

El problema es que tengo que configurarlo para cumplir con mis necesidades. pero no puedo cambiar el ObjectMapper que hace la magia para convertir el objeto a JSON, leyendo los 3 artículos a continuación, tengo la idea de que la única manera que puedo hacer es inyectar un Modificado ObjectMapper a Jersey. Ahora tengo lo que quiero.

Es como crear varias vistas en una tabla de base de datos.

Estos 3 enlaces le ayudará en diferentes formas:

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

Jersey, Jackson, Spring and JSON

Jersey + Spring integration example

recursos REST:

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); 
     } 
    } 

} 

modelo REST:

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... 

} 

RESTO vista del modelo:

package com.john.rest.model; 

public class SystemObjectView { 
    public static class ObjectList { }; 

    public static class ObjectDetail extends ObjectList { } 
} 
+3

Si pudiera votar a esto 20 veces que lo haría. Hasta que leí esto, no tenía idea de que pudiera aplicar JsonView al método de recursos Jersey para seleccionar la vista para serializar. Si eso está descrito en los documentos, lo extrañé por completo. Por cierto, funciona sin Spring (estoy usando Felix). ¡Muchas gracias! – sfitts

+0

No creo que te hayas perdido esto en los documentos. He estado buscando una buena manera de hacer esto durante mucho tiempo, y nunca encontré uno hasta que vi esta publicación. –

+0

Gracias a su informe, el proyecto README ahora incluye una breve descripción de las anotaciones que son aplicables a los puntos finales JAX-RS - https://github.com/FasterXML/jackson-jaxrs-providers - y con suerte otros contenedores (Spring, Restlet) tener/agregar algo similar. – StaxMan

Cuestiones relacionadas