27 votes

@JsonView, @JsonFilter et Spring de Jackson

Peut-on utiliser le Jackson @JsonView y @JsonFilter pour modifier le JSON renvoyé par un contrôleur Spring MVC, tout en utilisant les annotations MappingJacksonHttpMessageConverter et celui de Spring @ResponseBody y @RequestBody annotations ?

public class Product
{
    private Integer id;
    private Set<ProductDescription> descriptions;
    private BigDecimal price;
    ...
}

public class ProductDescription
{
    private Integer id;
    private Language language;
    private String name;
    private String summary;
    private String lifeStory;
    ...
}

Lorsque le client demande une collection de Products j'aimerais renvoyer une version minimale de chacun d'entre eux. ProductDescription peut-être juste son ID. Ensuite, lors d'un appel ultérieur, le client peut utiliser cet ID pour demander une instance complète de ProductDescription avec toutes les propriétés présentes.

L'idéal serait de pouvoir le spécifier sur les méthodes du contrôleur Spring MVC, car la méthode invoquée définit le contexte dans lequel le client demande les données.

11voto

doom777 Points 328

En fin de compte, nous voulons utiliser une notation similaire à celle que StaxMan a montrée pour JAX-RS. Malheureusement, Spring ne prend pas en charge ce type de notation, et nous devons donc le faire nous-mêmes.

Voici ma solution, elle n'est pas très jolie, mais elle fait l'affaire.

@JsonView(ViewId.class)
@RequestMapping(value="get", method=RequestMethod.GET) // Spring controller annotation
public Pojo getPojo(@RequestValue Long id)
{
   return new Pojo(id);
}

public class JsonViewAwareJsonView extends MappingJacksonJsonView {

    private ObjectMapper objectMapper = new ObjectMapper();

    private boolean prefixJson = false;

    private JsonEncoding encoding = JsonEncoding.UTF8;

    @Override
    public void setPrefixJson(boolean prefixJson) {
        super.setPrefixJson(prefixJson);
        this.prefixJson = prefixJson;
    }

    @Override
    public void setEncoding(JsonEncoding encoding) {
        super.setEncoding(encoding);
        this.encoding = encoding;
    }

    @Override
    public void setObjectMapper(ObjectMapper objectMapper) {
        super.setObjectMapper(objectMapper);
        this.objectMapper = objectMapper;
    }

    @Override
    protected void renderMergedOutputModel(Map<String, Object> model,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {

        Class<?> jsonView = null;
        if(model.containsKey("json.JsonView")){
            Class<?>[] allJsonViews = (Class<?>[]) model.remove("json.JsonView");
            if(allJsonViews.length == 1)
                jsonView = allJsonViews[0];
        }

        Object value = filterModel(model);
        JsonGenerator generator =
                this.objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), this.encoding);
        if (this.prefixJson) {
            generator.writeRaw("{} && ");
        }
        if(jsonView != null){
            SerializationConfig config = this.objectMapper.getSerializationConfig();
            config = config.withView(jsonView);
            this.objectMapper.writeValue(generator, value, config);
        }
        else
            this.objectMapper.writeValue(generator, value);
    }
}

public class JsonViewInterceptor extends HandlerInterceptorAdapter
{

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
        Object handler, ModelAndView modelAndView) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        JsonView jsonViewAnnotation = handlerMethod.getMethodAnnotation(JsonView.class);
        if(jsonViewAnnotation != null)
            modelAndView.addObject("json.JsonView", jsonViewAnnotation.value());
    }
}

Dans le fichier spring-servlet.xml

<bean name="ViewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="mediaTypes">
            <map>
                <entry key="json" value="application/json" />
            </map>
        </property>
        <property name="defaultContentType" value="application/json" />
        <property name="defaultViews">
            <list>
                <bean class="com.mycompany.myproject.JsonViewAwareJsonView">
                </bean>
            </list>
        </property>
    </bean>

y

<mvc:interceptors>
    <bean class="com.mycompany.myproject.JsonViewInterceptor" />
</mvc:interceptors>

10voto

StaxMan Points 34626

Je ne sais pas comment les choses fonctionnent avec Spring (désolé !), mais Jackson 1.9 peut utiliser l'annotation @JsonView à partir des méthodes JAX-RS, donc vous pouvez le faire :

@JsonView(ViewId.class)
@GET // and other JAX-RS annotations
public Pojo resourceMethod()
{
   return new Pojo();
} 

et Jackson utilisera la vue identifiée par ViewId.class comme vue active. Peut-être que Spring a (ou aura) une capacité similaire ? Avec JAX-RS, ceci est géré par le JacksonJaxrsProvider standard, pour ce que cela vaut.

10voto

George Siggouroglou Points 473

Ce problème est résolu !
Suivez ce

Ajout du support pour les vues de sérialisation Jackson

Spring MVC supporte maintenant les vues de sérialisation de Jackon pour rendre différents sous-ensembles d'un même POJO à partir de différentes méthodes différentes (par exemple, la page détaillée et la vue récapitulative). Problème : SPR-7156

C'est le SPR-7156 .

Statut : Résolu

Description

L'annotation JSONView de Jackson permet au développeur de contrôler quels aspects d'une méthode sont sérialisés. Avec l'implémentation actuelle, le rédacteur de vues de Jackson doit être utilisé mais le type de contenu n'est pas disponible. Il serait préférable de pouvoir spécifier une JSONView dans le cadre de l'annotation RequestBody.

Disponible sur Spring ver >= 4.1

UPDATE

Suivre cette lien . Explique avec un exemple l'annotation @JsonView.

3voto

user1137146 Points 150

En cherchant la même réponse, j'ai eu l'idée d'envelopper l'objet ResponseBody dans une vue.

Pièce de la classe du contrôleur :

@RequestMapping(value="/{id}", headers="Accept=application/json", method= RequestMethod.GET)
     public  @ResponseBody ResponseBodyWrapper getCompany(HttpServletResponse response, @PathVariable Long id){
        ResponseBodyWrapper responseBody =  new ResponseBodyWrapper(companyService.get(id),Views.Owner.class);
        return responseBody;
     }

public class ResponseBodyWrapper {
private Object object;
private Class<?> view;

public ResponseBodyWrapper(Object object, Class<?> view) {
    this.object = object;
    this.view = view;
}

public Object getObject() {
    return object;
}
public void setObject(Object object) {
    this.object = object;
}
@JsonIgnore
public Class<?> getView() {
    return view;
}
@JsonIgnore
public void setView(Class<?> view) {
    this.view = view;
}

}


Ensuite, je remplace writeInternal forme de méthode MappingJackson2HttpMessageConverter pour vérifier si l'objet à sérialiser est instanceof wrapper, si c'est le cas, je sérialise l'objet avec la vue requise.

public class CustomMappingJackson2 extends MappingJackson2HttpMessageConverter {

private ObjectMapper objectMapper = new ObjectMapper();
private boolean prefixJson;

@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
        throws IOException, HttpMessageNotWritableException {

    JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
    JsonGenerator jsonGenerator =
            this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
    try {
        if (this.prefixJson) {
            jsonGenerator.writeRaw("{} && ");
        }
        if(object instanceof ResponseBodyWrapper){
            ResponseBodyWrapper responseBody = (ResponseBodyWrapper) object;
            this.objectMapper.writerWithView(responseBody.getView()).writeValue(jsonGenerator, responseBody.getObject());
        }else{
            this.objectMapper.writeValue(jsonGenerator, object);
        }
    }
    catch (IOException ex) {
        throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
    }
}

public void setObjectMapper(ObjectMapper objectMapper) {
    Assert.notNull(objectMapper, "ObjectMapper must not be null");
    this.objectMapper = objectMapper;
    super.setObjectMapper(objectMapper);
}

public ObjectMapper getObjectMapper() {
    return this.objectMapper;
}

public void setPrefixJson(boolean prefixJson) {
    this.prefixJson = prefixJson;
    super.setPrefixJson(prefixJson);
}

}

3voto

Sparticles Points 152

La réponse à cette question, après de nombreuses impasses et des crises de rage des nerds, est..... tellement simple. Dans ce cas d'utilisation, nous avons un bean Client avec un objet complexe Adresse incorporé et nous voulons empêcher la sérialisation d'un nom de propriété banlieue et rue en adresse, lorsque la sérialisation json a lieu.

Pour ce faire, nous appliquons une annotation @JsonIgnoreProperties({"suburb"}) sur l'adresse du champ en le site Client Le nombre de champs à ignorer est illimité. Par exemple, je veux ignorer à la fois la banlieue et la rue. J'annoterai le champ adresse avec @JsonIgnoreProperties({"suburb", "street"})

En faisant tout cela, nous pouvons créer une architecture de type HATEOAS.

Voici le code complet

Client.java

public class Customer {

private int id;
private String email;
private String name;

@JsonIgnoreProperties({"suburb", "street"})
private Address address;

public Address getAddress() {
    return address;
}

public void setAddress(Address address) {
    this.address = address;
}

public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

}

Adresse.java public class Address {

private String street;
private String suburb;
private String Link link;

public Link getLink() {
    return link;
}

public void setLink(Link link) {
    this.link = link;
}

public String getStreet() {
    return street;
}

public void setStreet(String street) {
    this.street = street;
}

public String getSuburb() {
    return suburb;
}

public void setSuburb(String suburb) {
    this.suburb = suburb;
}

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X