Spring中如何按需隐藏JSON字段?@ResponseBody隐藏@ResponseEntity恢复
你遇到的这个场景其实很常见——同一个实体类,在不同接口返回时需要展示不同的字段,又不想重复创建DTO类。之前那些静态注解(比如@JsonIgnore、@JsonProperty(access=ReadOnly))之所以没生效,是因为它们都是全局生效的,没法根据返回方式(@ResponseBody/@ResponseEntity)动态切换。下面给你两种无需创建重复对象的解决方案,都是Jackson提供的动态序列化机制:
方法一:使用@JsonView(推荐,简洁直观)
这是Spring和Jackson集成时最常用的动态字段控制方式,核心是通过定义视图接口,标记哪些字段属于哪个视图,然后在接口方法上指定要使用的视图。
步骤1:定义视图接口
先创建两个视图接口,让“详情视图”继承“基础视图”,这样详情视图会自动包含基础视图的所有字段:
public class Views { // 基础视图:对应@ResponseBody返回时要显示的字段 public interface Base {} // 详情视图:对应@ResponseEntity返回时要额外显示的字段 public interface Detail extends Base {} }
步骤2:在实体类上标记字段所属视图
在需要控制的字段(或getter方法)上添加@JsonView注解,指定它属于哪个视图:
public class User { @JsonView(Views.Base.class) private Long id; @JsonView(Views.Base.class) private String username; // 这个字段只在Detail视图下显示 @JsonView(Views.Detail.class) private String password; // 省略getter、setter方法 }
步骤3:在Controller方法中指定视图
在返回@ResponseBody的方法上标记@JsonView(Views.Base.class),返回@ResponseEntity的方法上标记@JsonView(Views.Detail.class)即可:
@RestController public class UserController { // 使用@ResponseBody(@RestController已默认包含),只显示Base视图的字段(隐藏password) @GetMapping("/user/base") @JsonView(Views.Base.class) public User getBaseUser() { User user = new User(); user.setId(1L); user.setUsername("test"); user.setPassword("123456"); return user; } // 使用@ResponseEntity,显示Detail视图的字段(包含password) @GetMapping("/user/detail") @JsonView(Views.Detail.class) public ResponseEntity<User> getDetailUser() { User user = new User(); user.setId(1L); user.setUsername("test"); user.setPassword("123456"); return ResponseEntity.ok(user); } }
注意:如果是传统Spring MVC项目,需要确保
MappingJackson2HttpMessageConverter的ObjectMapper开启了DEFAULT_VIEW_INCLUSION=false,这样只会序列化标记了对应视图的字段;Spring Boot项目默认已经配置好了,不用额外操作。
方法二:使用@JsonFilter(更灵活,适合动态场景)
如果你需要更灵活的控制(比如根据请求参数决定显示哪些字段),可以用Jackson的过滤器机制。
步骤1:给实体类添加过滤器标记
@JsonFilter("UserFieldFilter") public class User { private Long id; private String username; private String password; // 省略getter、setter方法 }
步骤2:在Controller中动态设置过滤规则
通过MappingJacksonValue包装实体对象,动态指定要包含或排除的字段:
@RestController public class UserController { // @ResponseBody返回时,排除password字段 @GetMapping("/user/base") public MappingJacksonValue getBaseUser() { User user = new User(); user.setId(1L); user.setUsername("test"); user.setPassword("123456"); // 创建过滤器:序列化所有字段,除了password SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept("password"); FilterProvider filterProvider = new SimpleFilterProvider().addFilter("UserFieldFilter", filter); MappingJacksonValue result = new MappingJacksonValue(user); result.setFilters(filterProvider); return result; } // @ResponseEntity返回时,序列化所有字段(包含password) @GetMapping("/user/detail") public ResponseEntity<MappingJacksonValue> getDetailUser() { User user = new User(); user.setId(1L); user.setUsername("test"); user.setPassword("123456"); // 创建过滤器:序列化所有字段 SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAll(); FilterProvider filterProvider = new SimpleFilterProvider().addFilter("UserFieldFilter", filter); MappingJacksonValue result = new MappingJacksonValue(user); result.setFilters(filterProvider); return ResponseEntity.ok(result); } }
为什么之前的方法没生效?
@JsonIgnore:全局生效,一旦标记该字段,所有场景下都会被忽略,没法动态切换;@JsonProperty(access=Access.READ_ONLY):只是控制字段的反序列化权限(不能从JSON转对象),但序列化时还是会一直显示;@JsonIgnoreProperties:静态指定要忽略的字段,同样是全局生效,无法根据场景切换。
所以只有Jackson提供的动态视图/过滤机制,才能满足你同一对象在不同返回场景下显示不同字段的需求。
内容的提问来源于stack exchange,提问作者Adam

