在公司茶水间刷咖啡的时候,隔壁组的小王突然问我:“哥,你平时项目里用过@JsonView吗?我看文档里写着能过滤字段,可是我一脸懵逼啊。”我笑了笑说,这玩意儿其实特别适合咱们日常接口开发,尤其是 Spring Boot 项目里经常要根据不同场景返回不同数据,很多人第一反应就是自定义 DTO 或者写一堆转换逻辑,其实原生注解就能轻松搞定,优雅得一批。
什么是 @JsonView?
简单点说,@JsonView是 Jackson 提供的一个注解,用来在序列化对象时动态过滤字段。比如同一个User对象,有时候你只想返回id和name,有时候又要把email也返回出来,甚至在后台管理接口里要完整信息,这时候写多个 DTO 就太麻烦了。@JsonView直接定义几套“视图”,然后在控制器里用不同的视图来序列化,字段就会自动过滤掉。
举个例子,先定义一个用户实体:
import com.fasterxml.jackson.annotation.JsonView;
publicclass User {
publicinterface SimpleView {}
publicinterface DetailView extends SimpleView {}
@JsonView(SimpleView.class)
private Long id;
@JsonView(SimpleView.class)
private String name;
@JsonView(DetailView.class)
private String email;
@JsonView(DetailView.class)
private String address;
// getter & setter
}
上面代码里,我定义了两个视图:SimpleView和DetailView。DetailView继承了SimpleView,意思就是它包含了基础信息 + 详细信息。
在 Controller 里使用
再看 Controller 层的用法。比如有两个接口:一个是获取公开用户信息,一个是获取用户详细信息。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.annotation.JsonView;
@RestController
publicclass UserController {
@GetMapping("/user/simple")
@JsonView(User.SimpleView.class)
public User getSimpleUser() {
User u = new User();
u.setId(1L);
u.setName("Tom");
u.setEmail("tom@example.com");
u.setAddress("Beijing");
return u;
}
@GetMapping("/user/detail")
@JsonView(User.DetailView.class)
public User getDetailUser() {
User u = new User();
u.setId(1L);
u.setName("Tom");
u.setEmail("tom@example.com");
u.setAddress("Beijing");
return u;
}
}
当你访问/user/simple,返回结果是:
{
"id": 1,
"name": "Tom"
}
访问/user/detail,返回的就是完整信息:
{
"id": 1,
"name": "Tom",
"email": "tom@example.com",
"address": "Beijing"
}
这时候小王点点头,说“哦,这不就是免去了 DTO 的锅嘛!”我说对啊,很多场景特别省事。
为什么说它优雅?
你想啊,如果不用@JsonView,大多数人会这么搞:
新建一个UserSimpleDTO
再建一个UserDetailDTO
在 Service 层把User转换成这几个 DTO
最后再返回
这流程写多了就烦了,尤其是实体字段变动时,DTO 也得跟着改,成本很高。反而@JsonView直接标记在字段上,一劳永逸。
而且因为@JsonView是 Jackson 原生支持的,Spring Boot 里也无缝集成,完全不用额外依赖或者复杂配置。
高级用法:结合集合和泛型
有人问,那如果返回的是集合呢?比如一个List<User>?很简单,@JsonView依然有效。Spring 在序列化的时候会统一应用视图规则。
@GetMapping("/users/simple")
@JsonView(User.SimpleView.class)
public List<User> getSimpleUsers() {
return Arrays.asList(
new User(1L, "Tom", "tom@example.com", "Beijing"),
new User(2L, "Jerry", "jerry@example.com", "Shanghai")
);
}
结果就是过滤后的简化视图。
注意事项
不过用的时候也有一些坑。比如:
如果 Controller 方法没加@JsonView,默认会序列化所有字段。
如果你在返回对象上没加任何@JsonView注解,那@JsonView就相当于失效。
当一个字段打了多个视图注解时,Spring 会根据你指定的视图去决定是否输出。
再有一个小技巧是,你可以在DetailView里继承SimpleView,就不用重复写注解了,很符合层级化的场景。
实际应用场景
举几个工作中常见的:
前端展示:对外接口只需要展示用户昵称和头像,这时候用SimpleView。
管理后台:需要返回更多字段,比如手机号、邮箱等,就用DetailView。
内部接口:可能还要返回一些敏感字段,比如注册 IP,这就可以定义一个更深的视图。
这样下来,代码简洁,逻辑清晰,字段控制粒度也够。
和 @JsonIgnore 的区别
有的同学可能会疑惑,那我直接用@JsonIgnore不就完了?确实,@JsonIgnore能永久忽略某字段,但它是“全局忽略”,一旦标记就再也返回不了了。而@JsonView则是“按需返回”,灵活多了。
总结
Spring Boot 配合 Jackson 的@JsonView,可以说是轻量级字段过滤的神器,不需要造 DTO,不需要写复杂的转换逻辑,就能应对不同的返回场景。写法简单,维护成本低,完全值得在项目里推广。
不过话说回来,如果场景特别复杂,比如对象间关系过深、接口差异过大,DTO 还是有它的价值的,两者不是互斥关系,而是可以结合使用。
小王听完之后眼睛一亮:“这也太优雅了吧!”我笑着说,“下次你再写接口,就别一上来造 DTO 了,先想想能不能用@JsonView。”说完我咖啡凉了,赶紧回去敲代码了。