2012-06-01 13 views
13

Sto utilizzando Jersey per implementare servizi in stile REST JAX-RS insieme a Jackson 2.0.2 per la mappatura JSON. Uno di questi servizi REST restituisce un List<EntityA> (chiamiamolo indexA) dove EntityA contiene un altro List<EntityB> mentre un altro servizio restituisce solo un List<EntityB> (chiamiamolo indexB):Applicazione di diversi filtri Jackson per chiamate di servizio Jersey REST diverse

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

Quello che mi piacerebbe raggiungere è quello di applicare un filtro Jackson per l'invocazione indexA in modo che non tutti gli attributi degli elementi figlio EntityB siano serializzati. OTOH, indexB dovrebbe restituire EntityB nella sua completezza.

Sono a conoscenza dell'esistenza di uno ContextResolver<ObjectMapper>, che sto già utilizzando per altri scopi. Sfortunatamente, per il ContextResolver sembra impossibile distinguere entrambe le chiamate di servizio poiché lo Class fornito a ContextResolver.getContext(Class) è ArrayList in entrambi i casi (e grazie alla cancellazione del tipo non riesco a capire i parametri di tipo generico).

Esistono alcuni ganci più adatti alla configurazione di ObjectMapper/FilterProvider in base al tipo di entità che viene mappata?

Potrei usare l'approccio proposto in How to return a partial JSON response using Java?: mappare manualmente su un String, ma questo uccide l'intera bellezza di un approccio basato su annotazioni dichiarative, quindi mi piacerebbe evitare questo.

risposta

27

Ero nella stessa situazione, dopo tonnellate di ricerche, l'ho capito, la soluzione è usare @JsonView e Spring che può iniettare uno ObjectMapper nello JSON Writer senza uccidere la bellezza di Jersey.

Sto lavorando su un set di API REST, voglio ottenere un elenco di istanze di SystemObject e il dettaglio un'istanza specifica di SystemObject, proprio come te, voglio solo molto limitato del numero di proprietà di ogni istanza nel elenca alcune proprietà aggiuntive nei dettagli, definisco semplicemente le visualizzazioni per loro e aggiungo annotazioni nella classe SystemObject. ma, per impostazione predefinita, tutte le proprietà senza annotazione @JsonView verranno inviate a JSON, ma è disponibile una configurazione item(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION) che posso utilizzare per escluderle.

Il problema è che devo impostarlo su true per soddisfare le mie esigenze. ma non posso cambiare l'ObjectMapper che fa la magia per convertire l'oggetto in JSON, leggendo i 3 articoli seguenti, ho avuto l'idea che l'unico modo che posso fare è iniettare un Modificato ObjectMapper a Jersey. Ora ho quello che voglio.

È come se si creino più viste rispetto a una tabella di database.

Questi 3 link vi aiuterà in diversi modi:

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

Jersey, Jackson, Spring and JSON

Jersey + Spring integration example

risorsa RIPOSO:

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

} 

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

} 

REST vista del modello:

package com.john.rest.model; 

public class SystemObjectView { 
    public static class ObjectList { }; 

    public static class ObjectDetail extends ObjectList { } 
} 
+3

Se potessi votare fino questa 20 volte lo farei. Fino a quando ho letto questo non avevo idea che si potesse applicare JsonView al metodo di risorsa Jersey per selezionare la vista da serializzare con. Se ciò è descritto nella documentazione, l'ho completamente perso. BTW - funziona senza Spring (sto usando Felix). Grazie mille! – sfitts

+0

Non credo ci sia mancato questo nei documenti. Ho cercato un buon modo per farlo a lungo, e non l'ho mai trovato prima di aver visto questo post. –

+0

Grazie alla segnalazione, il progetto README ora include una breve descrizione delle annotazioni applicabili agli endpoint JAX-RS - https://github.com/FasterXML/jackson-jaxrs-providers - e si spera che altri contenitori (Spring, Restlet) avere/aggiungere qualcosa di simile. – StaxMan

Problemi correlati