Jackson 的@JsonView、@JsonFilter 和 Spring
Posted
技术标签:
【中文标题】Jackson 的@JsonView、@JsonFilter 和 Spring【英文标题】:Jackson's @JsonView, @JsonFilter and Spring 【发布时间】:2011-11-30 01:11:30 【问题描述】:是否可以使用 Jackson @JsonView
和 @JsonFilter
注释来修改 Spring MVC 控制器返回的 JSON,同时使用 MappingJacksonHttpMessageConverter
和 Spring 的 @ResponseBody
和 @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
的集合时,我想返回每个ProductDescription
的最小版本,也许只是它的ID。然后在随后的调用中,客户端可以使用此 ID 请求具有所有属性的 ProductDescription
的完整实例。
最好能够在 Spring MVC 控制器方法上指定这一点,因为调用的方法定义了客户端请求数据的上下文。
【问题讨论】:
我有同样的问题。@StaxMan 回答有帮助,但没有回答。Jackson 库现在可能不支持这个。 这也是相关的***.com/questions/5772304/… 请在这里投票:jira.springsource.org/browse/… 【参考方案1】:我不知道 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,这是由标准的 JacksonJaxrsProvider 处理的,物有所值。
【讨论】:
【参考方案2】:最终,我们希望使用类似于 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>
【讨论】:
你在哪里注册jsonViewInterceptor? 对不起,我现在添加了注册。【参考方案3】:除了@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);
【讨论】:
【参考方案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);
【讨论】:
非常好的方法。对于 Web 服务,使用转换器比使用视图更干净(尽管您当然可以使用任何一种)。您的 ResponseBodyWrapper 是一种将 JSON 视图传达给转换器的创造性方式。 唯一轻微的遗憾是控制器必须返回一些不是要序列化的对象的东西。换句话说,它与客户转换器有些紧密的联系。在我的情况下,我可以忍受,但很高兴知道。 像魅力一样工作!感谢分享。【参考方案5】:在经历了许多头撞死角和书呆子愤怒发脾气之后,这个问题的答案是...... 很简单。在这个用例中,我们有一个 Customer bean,其中嵌入了一个复杂的对象 Address,当发生 json 序列化时,我们希望防止地址中的属性名称 surburb and street 序列化。
我们通过在 Customer 类的 in 字段地址上应用注释 @JsonIgnoreProperties("suburb") 来做到这一点,要忽略的字段数量是无限的。例如,我想忽略郊区和街道。我会用 @JsonIgnoreProperties("suburb", "street")
注释地址字段通过这样做,我们可以创建 HATEOAS 类型的架构。
下面是完整代码
客户.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;
地址.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;
【讨论】:
我认为@JsonIgnoreProperties 只能应用于类,不能应用于属性。你的代码不会为我编译。 这取决于您使用的新版本允许对字段进行注释的版本,请自行查看java doc。 fasterxml.github.io/jackson-annotations/javadoc/2.0.0/com/… 问题是您可能有不同的序列化配置文件,并且地址可能有返回客户的链接。 我认为这是针对这种特定情况的最佳解决方案。它帮助了我(比 JsonView 更好),而且我的情况与给定的情况非常相似【参考方案6】:这个问题解决了! 关注this
添加对 Jackson 序列化视图的支持
Spring MVC 现在支持 Jackon 的序列化视图进行渲染 来自不同控制器的相同 POJO 的不同子集 方法(例如详细页面与摘要视图)。 问题:SPR-7156
这是SPR-7156。
状态:已解决
说明
Jackson 的 JSONView 注释允许开发人员控制方法的哪些方面被序列化。对于当前的实现,必须使用 Jackson 视图编写器,但内容类型不可用。如果作为 RequestBody 注解的一部分,可以指定 JSONView 会更好。
可在Spring ver >= 4.1
更新
关注此link。举例说明@JsonView 注解。
【讨论】:
有没有使用新功能的例子?【参考方案7】:更好的是,从 4.2.0.Release 开始,您可以简单地执行以下操作:
@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
【讨论】:
您能否详细说明这种方法或指出一个完整的示例?我同意这看起来是处理事情的正确方法- 这里有一个稍微详细一点的例子:github.com/krishna81m/jackson-nested-prop-filter 如果性能很重要,这个项目还有一个更简单的替代方案。 谢谢!我会检查这个-以上是关于Jackson 的@JsonView、@JsonFilter 和 Spring的主要内容,如果未能解决你的问题,请参考以下文章
@JsonView 不过滤属性(Spring 4.1.0.RC2,Jackson 2.3.2)
css [JSONView Solarized Dark]用于JSONView扩展的Solarized Dark配色方案。 #solarized #jsonview