Java 杰克逊';s@JsonView、@JsonFilter和Spring
在使用映射JacksonHttpMessageConverter和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
@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;
}