Jackson教程
使用JsonView视图过滤字段实现字段分组

首先,一起回顾一下几种忽略字段、过滤字段的方式。

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)