Java 杰克逊';s@JsonView、@JsonFilter和Spring

Java 杰克逊';s@JsonView、@JsonFilter和Spring,java,json,spring,spring-mvc,jackson,Java,Json,Spring,Spring Mvc,Jackson,在使用映射JacksonHttpMessageConverter和Spring的@ResponseBody和@RequestBody注释的同时,可以使用Jackson@JsonView和@JsonFilter注释来修改Spring MVC控制器返回的JSON吗 public class Product { private Integer id; private Set<ProductDescription> descriptions; private BigD

在使用映射JacksonHttpMessageConverter和Spring的
@ResponseBody
@RequestBody
注释的同时,可以使用Jackson
@JsonView
@JsonFilter
注释来修改Spring MVC控制器返回的JSON吗

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;
    ...
}
公共类产品
{
私有整数id;
私有集描述;
私人价格;
...
}
公共类产品描述
{
私有整数id;
私人语言;
私有字符串名称;
私有字符串摘要;
私人生活史;
...
}
当客户端请求一组
产品时,我想返回每个
产品描述
的最低版本,可能只是它的ID。然后在后续调用中,客户端可以使用此ID请求所有属性都存在的
产品描述
的完整实例


最好能够在Spring MVC控制器方法上指定这一点,因为调用的方法定义了客户端请求数据的上下文。

我不知道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标识的视图作为活动视图。也许Spring有(或将有)类似的功能?对于JAX-RS,这是由标准JacksonJAXRProvider处理的,这是值得的。

最终,我们希望使用类似于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);
}


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


除了@user356083之外,我还做了一些修改,以便在返回@ResponseBody时使这个示例正常工作。使用ThreadLocal有点像黑客,但是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); 
        } 
    }
公共类视图线程{
私有静态最终线程本地[]>();
私有静态最终日志日志=LogFactory.getLog(SocialRequestUtils.class);
公共静态无效设置键(类[]键){
viewThread.set(键);
} 
公共静态类[]getKey(){
if(viewThread.get()==null)
log.error(“缺少threadLocale变量”);
返回viewThread.get();
} 
} 
公共类JsonViewInterceptor扩展了HandlerInterceptorAdapter{
@凌驾
公共布尔预处理(
HttpServletRequest请求,
HttpServletResponse,
对象处理程序){
HandlerMethod HandlerMethod=(HandlerMethod)处理程序;
JsonView jsonViewAnnotation=handlerMethod
.getMethodAnnotation(JsonView.class);
if(jsonViewAnnotation!=null)
setKey(jsonViewAnnotation.value());
返回true;
} 
} 
公共类MappingJackson2HttpMessageConverter扩展
AbstractHttpMessageConverter{
@凌驾
受保护的void writeInternal(对象对象,HttpOutputMessage outputMessage)
抛出IOException,HttpMessageNotWritableException{
JsonEncoding encoding=getJsonEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator JsonGenerator=
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(),编码);
//这是由ObjectMapper#getJsonFactory创建的JsonGenerators这一事实的解决方法
//未应用ObjectMapper序列化功能。
//看https://github.com/FasterXML/jackson-databind/issues/12 
if(objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)){
jsongGenerator.useDefaultPrettyPrinter();
} 
//有点像黑客。
类[]jsonViews=ViewThread.getKey();
ObjectWriter=null;
如果(jsonViews!=null){
writer=this.objectMapper.writerWithView(jsonViews[0]);
}否则{
writer=this.objectMapper.writer();
} 
试试{
如果(this.prefixJson){
jsonGenerator.writeRaw(“{}&”);
} 
writeValue(jsonGenerator,对象);
} 
catch(JsonProcessingException ex){
抛出新的HttpMessageNotWritableException(“无法写入JSON:+ex.getMessage(),ex”);
} 
}

为了寻找相同的答案,我想出了一个主意,用视图包装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;
     }


}

在经历了无数次头痛的死胡同和书呆子的暴怒之后,答案是。。。。 这么简单。在这个用例中,我们有一个客户bean,其中嵌入了一个复杂的对象地址,我们希望在json序列化发生时,防止在地址中序列化属性名surburb和street

我们通过在客户类中的字段地址上应用注释@JsonIgnoreProperties({“郊区”}),可以忽略的字段数量是无限的。e、 我想去郊区和街道。我会用@JsonIgnoreProperties({“郊区”、“街道”)注释地址字段

通过这样做,我们可以创建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;
}
}这个问题解决了
跟随

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

SpringMVC现在支持Jackon的序列化视图进行渲染 来自不同控制的同一POJO的不同子集
@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;
}
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);
}
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;
}
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;
}
@RequestMapping(method = RequestMethod.POST, value = "/springjsonfilter")
    public @ResponseBody MappingJacksonValue byJsonFilter(...) {
        MappingJacksonValue jacksonValue = new MappingJacksonValue(responseObj);    
        jacksonValue.setFilters(customFilterObj);
        return jacksonValue;
    }