杰克逊的@JsonView,@ JsonFilter和Spring

时间:2011-10-03 15:57:53

标签: java json spring spring-mvc jackson

可以使用Jackson @JsonView@JsonFilter注释来修改Spring MVC控制器返回的JSON,同时使用MappingJacksonHttpMessageConverter和Spring的@ResponseBody和{{1}注释?

@RequestBody

当客户端请求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; ... } 的集合时,我想返回每个Products的最小版本,可能只是其ID。然后在后续调用中,客户端可以使用此ID来请求包含所有属性的ProductDescription的完整实例。

能够在Spring MVC控制器方法上指定它是理想的,因为调用的方法定义了客户端请求数据的上下文。

7 个答案:

答案 0 :(得分:13)

最终,我们希望使用类似于StaxMan为JAX-RS显示的符号。不幸的是,Spring不支持开箱即用,所以我们必须自己做。

这是我的解决方案,它不是很漂亮,但它可以完成这项任务。

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

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

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

答案 1 :(得分:13)

这个问题解决了!
关注this

  

添加对Jackson序列化视图的支持

     

Spring MVC现在支持Jackon的序列化视图进行渲染   来自不同控制器的相同POJO的不同子集   方法(例如详细页面与摘要视图)。   问题:SPR-7156

这是SPR-7156

  

状态:已解决

     

<强>描述

     

Jackson的JSONView注释允许开发人员控制方法的哪些方面是序列化的。在当前实现中,必须使用Jackson视图编写器,但是内容类型不可用。如果作为RequestBody注释的一部分,可以更好地指定JSONView。

Spring ver >= 4.1

上提供

更新

关注此link。用一个例子解释@JsonView注释。

答案 2 :(得分:12)

我不知道Spring的工作原理(对不起!),但Jackson 1.9可以使用JAX-RS方法中的@JsonView注释,所以你可以这样做:

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

并且Jackson将使用ViewId.class标识的View作为活动视图。也许Spring有(或将有)类似的能力?使用JAX-RS,这是由标准的JacksonJaxrsProvider处理的,这是值得的。

答案 3 :(得分:4)

寻找相同的答案我想出了用视图包装ResponseBody对象的想法。

控制器类:

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

}


然后我重写writeInternal方法形式MappingJackson2HttpMessageConverter以检查序列化的对象是否是instanceof包装器,如果是,我将序列化具有所需视图的对象。

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

}

答案 4 :(得分:4)

在许多头部撞击死胡同和书呆子愤怒的发脾气后,答案是.... 很简单。在这个用例中,我们有一个Customer bean,其中嵌入了一个复杂的对象Address,我们希望在json序列化时阻止在地址中序列化属性名称 surburb和street

我们通过在客户类的 字段中应用注释 @JsonIgnoreProperties({“suburb”})来实现此目的。要忽略的字段数是无限的。例如,我想进入郊区和街道。我会用 @JsonIgnoreProperties({“suburb”,“street”})注释地址字段

通过这一切,我们可以创建HATEOAS类型的架构。

以下是完整代码

Customer.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;
}

}

<强> Address.java 公共类地址{

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

}

答案 5 :(得分:2)

除了@ user356083之外,我还做了一些修改,以便在返回@ResponseBody时使这个例子有效。这是一个使用ThreadLocal的hack,但Spring似乎没有提供必要的上下文来做到这一点。

public class ViewThread { 

    private static final ThreadLocal<Class<?>[]> viewThread = new ThreadLocal<Class<?>[]>(); 

    private static final Log log = LogFactory.getLog(SocialRequestUtils.class); 

    public static void setKey(Class<?>[] key){ 
        viewThread.set(key); 
    } 

    public static Class<?>[] getKey(){ 
        if(viewThread.get() == null) 
            log.error("Missing threadLocale variable"); 

        return viewThread.get(); 
    } 
} 

public class JsonViewInterceptor extends HandlerInterceptorAdapter { 

    @Override 
    public boolean preHandle( 
            HttpServletRequest request, 
            HttpServletResponse response, 
            Object handler) { 

        HandlerMethod handlerMethod = (HandlerMethod) handler; 

        JsonView jsonViewAnnotation = handlerMethod 
                .getMethodAnnotation(JsonView.class); 

        if (jsonViewAnnotation != null) 
            ViewThread.setKey(jsonViewAnnotation.value()); 

        return true; 
    } 
} 

public class MappingJackson2HttpMessageConverter extends 
        AbstractHttpMessageConverter<Object> { 

    @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); 
        // This is a workaround for the fact JsonGenerators created by ObjectMapper#getJsonFactory 
        // do not have ObjectMapper serialization features applied. 
        // See https://github.com/FasterXML/jackson-databind/issues/12 
        if (objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) { 
            jsonGenerator.useDefaultPrettyPrinter(); 
        } 

        //A bit of a hack.  
        Class<?>[] jsonViews = ViewThread.getKey(); 

        ObjectWriter writer = null; 

        if(jsonViews != null){ 
            writer = this.objectMapper.writerWithView(jsonViews[0]); 
        }else{ 
            writer = this.objectMapper.writer(); 
        } 

        try { 
            if (this.prefixJson) { 
                jsonGenerator.writeRaw("{} && "); 
            } 

            writer.writeValue(jsonGenerator, object); 

        } 
        catch (JsonProcessingException ex) { 
            throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); 
        } 
    }

答案 6 :(得分:2)

更好,从4.2.0开始。你可以这样做:

@RequestMapping(method = RequestMethod.POST, value = "/springjsonfilter")
    public @ResponseBody MappingJacksonValue byJsonFilter(...) {
        MappingJacksonValue jacksonValue = new MappingJacksonValue(responseObj);    
        jacksonValue.setFilters(customFilterObj);
        return jacksonValue;
    }

参考文献:  1. https://jira.spring.io/browse/SPR-12586  2. http://wiki.fasterxml.com/JacksonFeatureJsonFilter