首先,一起回顾一下几种忽略字段、过滤字段的方式。
JsonIgnore和JsonIgnoreProperties注解,可以用于忽略指定的字段。而JsonIgnoreType注解,可以忽略特定数据类型的字段。
使用JsonFilter过滤器注解,可以动态的设置包含或排除字段,支持自定义过滤规则。
本篇要讲的,是另一个注解JsonView。从名字上看,这是一个视图。视图通常有快照,或不同视图能看到不同的数据等概念。
在Jackson中,JsonView注解用于指定哪些视图能够序列化哪些字段。也就是说,只有当一个字段添加了视图的注解,在使用该视图进行序列化或反序列化时才会包含该字段。
本篇内容基于Jackson 2.11.2版本,马上开始学习吧。
使用JsonView注解
定义一个代表视图的类Summary,不包含任何字段和方法的空实现。事实上,可以理解为只是定义了一个标识,只不过是用类来做标识。
public class MyJsonViews {
public static class Summary {}
}
为字段添加JsonView注解,将字段和视图Summary进行关联。这使得在使用Summary视图序列化时,包含关联的字段。
public class Article {
@JsonView(MyJsonViews.Summary.class)
private String title;
@JsonView(MyJsonViews.Summary.class)
private String summary;
private String content;
// 省略getter、setter方法
}
在序列化的时候,需要先调用ObjectMapper的writerWithView方法设置视图类的class对象。
/**
* 默认情况下,没有添加@JsonView注解的字段,也会被序列化
*
* @throws JsonProcessingException
*/
@Test
public void noConfig() throws JsonProcessingException {
Article article = new Article();
article.setTitle("title");
article.setSummary("summary");
article.setContent("content");
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writerWithView(MyJsonViews.Summary.class).writeValueAsString(article);
System.out.println(jsonString);
}
执行结果:
{"title":"title","summary":"summary","content":"content"}
结果多了一个content字段,这个字段并没有添加视图注解。
这是因为在默认情况下,没有添加JsonView注解的字段也会被序列化。
正确的方法是,在序列化之前设置禁止序列化没有注解的字段(MapperFeature.DEFAULT_VIEW_INCLUSION)。
/**
* 没有添加@JsonView注解的字段不会被序列化
*
* @throws JsonProcessingException
*/
@Test
public void disableViewInclusion() throws JsonProcessingException {
Article article = new Article();
article.setTitle("title");
article.setSummary("summary");
article.setContent("content");
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION); // 禁止序列化没有注解的字段
String jsonString = mapper.writerWithView(MyJsonViews.Summary.class).writeValueAsString(article);
System.out.println(jsonString);
}
执行结果:
{"title":"title","summary":"summary"}
一个注解多个视图
如果有多个视图需要包含同一个字段,可以在同一个JsonView注解传入多个视图标识。
public class MyJsonViews {
public static class Welcome {}
public static class Summary {}
}
public class ArticleWithMultiViews {
@JsonView({MyJsonViews.Summary.class, MyJsonViews.Welcome.class})
private String title;
@JsonView(MyJsonViews.Summary.class)
private String summary;
private String content;
// 省略getter、setter方法
}
/**
* 同一个JsonView注解,可以指定多个视图
*
* @throws JsonProcessingException
*/
@Test
public void multiViews() throws JsonProcessingException {
ArticleWithMultiViews article = new ArticleWithMultiViews();
article.setTitle("title");
article.setSummary("summary");
article.setContent("content");
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION); // 禁止序列化没有注解的字段
String jsonString = mapper.writerWithView(MyJsonViews.Summary.class).writeValueAsString(article);
System.out.println(jsonString);
jsonString = mapper.writerWithView(MyJsonViews.Welcome.class).writeValueAsString(article);
System.out.println(jsonString);
}
执行结果:
{"title":"title","summary":"summary"}
{"title":"title"}
视图支持继承
有些时候,多个视图之间是有关联关系的。
例如一个系统的用户,分为访客、普通用户和管理员。由于权限不同,每类用户能看到的数据是不同的。但是通常来说,普通用户能够看到访客看到的内容,管理员能够看到访客和普通用户能看到的内容。
用户之间的这种权限关系,可以用类的继承来表达。
Jackson的视图支持继承,作用就类似于用户查看数据的权限继承关系。
重新定义一视图类,让Detail继承Summary。
public class MyJsonViews {
// 概要-普通用户
public static class Summary {}
// 详情-管理员
public static class Detail extends Summary {}
}
public class ArticleWithExtendView {
@JsonView(MyJsonViews.Summary.class)
private String title;
@JsonView(MyJsonViews.Summary.class)
private String summary;
@JsonView(MyJsonViews.Detail.class)
private String content;
// 省略getter、setter方法
@Override
public String toString() {
return "ArticleWithExtendView [title=" + title + ", summary=" + summary + ", content=" + content + "]";
}
}
/**
* 不同视图之间可以相互继承,子视图将会包含父视图注解的字段
*
* @throws JsonProcessingException
*/
@Test
public void extendView() throws JsonProcessingException {
ArticleWithExtendView article = new ArticleWithExtendView();
article.setTitle("title");
article.setSummary("summary");
article.setContent("content");
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION); // 禁止序列化没有注解的字段
String jsonString = mapper.writerWithView(MyJsonViews.Summary.class).writeValueAsString(article);
System.out.println(jsonString);
// 由于Detail继承了Summary,所以能被Summary视图序列化的字段,也会被Detail视图序列化。
jsonString = mapper.writerWithView(MyJsonViews.Detail.class).writeValueAsString(article);
System.out.println(jsonString);
}
执行结果:
{"title":"title","summary":"summary"}
{"title":"title","summary":"summary","content":"content"}
由于Detail继承了Summary,所以能被Summary视图序列化的字段,也会被Detail视图序列化。
也就是说,子视图会包含父视图的字段。
视图支持反序列化
视图也支持反序列化操作,使用前需要先调用ObjectMapper的readerWithView方法,设置视图类的class对象。
/**
* 视图同样可用于反序列化
*
* @throws IOException
*/
@Test
public void deserialize() throws IOException {
String jsonString = "{\"title\":\"title\",\"summary\":\"summary\",\"content\":\"content\"}";
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION); // 禁止序列化没有注解的字段
ArticleWithExtendView article = mapper.readerWithView(MyJsonViews.Summary.class).readValue(jsonString, ArticleWithExtendView.class);
// ArticleWithExtendView article = mapper.readerWithView(MyJsonViews.Summary.class).forType(ArticleWithExtendView.class).readValue(jsonString);
System.out.println(article.toString());
}
执行结果:
ArticleWithExtendView [title=title, summary=summary, content=null]
小结
Jackson为过滤字段提供了多种实现方式,每种方式都是自身的优点和缺点。
具体需要使用哪种过滤方式,可以结合实际的应用场景来评估。
JsonView具有视图的概念,比较适合于需要将序列化的字段分组的场景。
参考
https://github.com/FasterXML/jackson (opens in a new tab)
https://www.baeldung.com/jackson-json-view-annotation (opens in a new tab)
https://www.logicbig.com/tutorials/misc/jackson/json-view-annotation.html (opens in a new tab)