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具有视图的概念,比较适合于需要将序列化的字段分组的场景。

参考

《轻松学习Jackson》程序员口袋里的开发手册

https://github.com/FasterXML/jackson

https://www.baeldung.com/jackson-json-view-annotation

https://www.logicbig.com/tutorials/misc/jackson/json-view-annotation.html


---转载本站文章请注明作者和出处 996极客教程(996geek.com),请勿用于任何商业用途---

留下评论