上篇|Jackson注解的用法和场景,不看巨亏

举报
码农小胖哥 发表于 2022/03/31 23:19:24 2022/03/31
【摘要】 Jackson注解一览 今天总结一下Jackson的一系列注解的用法和场景,或许能帮助你实现一些功能,总结不易,还请多多关注、点赞、转发。 @JacksonAnnotation 这个注解经常用于Jackson自定义注解中,用来标记这是一个Jackson注解,这个胖哥在Jackson脱敏一文中用过它来实现自定义的序列化注解...

Jackson注解一览

今天总结一下Jackson的一系列注解的用法和场景,或许能帮助你实现一些功能,总结不易,还请多多关注、点赞、转发。

@JacksonAnnotation

这个注解经常用于Jackson自定义注解中,用来标记这是一个Jackson注解,这个胖哥在Jackson脱敏一文中用过它来实现自定义的序列化注解。

@JacksonAnnotationsInside

这个注解用来标记Jackson复合注解,当你使用多个Jackson注解组合成一个自定义注解时会用到它。


   
  1.     /**
  2.      * 非空以及忽略未知属性
  3.      **/
  4.     @Retention(RetentionPolicy.RUNTIME)
  5.     @JacksonAnnotationsInside
  6.     @JsonInclude(Include.NON_NULL)
  7.     @JsonIgnoreProperties(ignoreUnknown = true)
  8.     public @interface NotNullAndIgnoreAnnotation {}

@JacksonInject

json属性值将在反序列化时可以被注入,我们先在属性上标记:


   
  1. @Data
  2. public final class JacksonInjectUser {
  3.     @JacksonInject(value = "dynamic")
  4.     private String name;
  5.     private Integer age;
  6. }

然后name的值就可以在反序列化的时候动态化,不再需要去解析、拼字段。


   
  1.     @SneakyThrows
  2.     @Test
  3.     void jacksonInject() {
  4.         // 这个值动态化了
  5.         String dynamicValue = "some Dynamic value";
  6.         InjectableValues.Std injectableValues = new InjectableValues.Std()
  7.                 // 名称和注解中声明的相同才行
  8.                 .addValue("dynamic", dynamicValue);
  9.         JacksonInjectUser jacksonInjectUser = objectMapper.setInjectableValues(injectableValues)
  10.                 // 空json 最后居然可以赋值
  11.                 .readValue("{}", JacksonInjectUser.class);
  12.         Assertions.assertEquals(dynamicValue,jacksonInjectUser.getName());
  13.     }

注意@JacksonInject中提供了useInput参数进行绑定策略控制。

@JsonAlias

在反序列化的时候来对Java Bean的属性进行名称绑定,可以绑定多个json的键名。举个例子:


   
  1.     @SneakyThrows
  2.     @Test
  3.     void jsonAlias(){
  4.         // 两个json的类型结构是相同的 可以定义一个Bean来接收
  5.         String userJson = "{\"name\": \"felord.cn\",\"age\": 22}";
  6.         String itemJson = "{\"category\": \"coco\", \"count\": 50 }";
  7.         Domain user = objectMapper.readValue(userJson, Domain.class);
  8.         Assertions.assertEquals("felord.cn",user.getStr());
  9.         Assertions.assertEquals(22,user.getNum());
  10.         Domain item = objectMapper.readValue(itemJson, Domain.class);
  11.         Assertions.assertEquals("coco",item.getStr());
  12.         Assertions.assertEquals(50,item.getNum());
  13.     }
  14. @Data
  15. public class Domain{
  16.     @JsonAlias({"name","category"})
  17.     private String str;
  18.     @JsonAlias({"age","count"})
  19.     private Integer num;
  20. }

注意:只能用于json反序列化。

@JsonAnyGetter

在json序列化时可以将Bean中的java.util.Map类型的属性“平铺展开”,举个例子:

某个Java Bean正常的json序列化结果是:


   
  1. {
  2.   "name""felord.cn",
  3.   "age"22,
  4.   "unMatched": {
  5.     "unknown""unknown"
  6.   }
  7. }

但是我们需要:


   
  1. {
  2.   "name""felord.cn",
  3.   "age"22,
  4.   "unknown""unknown"
  5. }

我们可以对Java Bean这么标记:


   
  1. @Data
  2. public class MapUser {
  3.     private String name;
  4.     private Integer age;
  5.     private Map<String,Object> unMatched;
  6.     @JsonAnyGetter
  7.     public Map<String, Object> getUnMatched() {
  8.         return unMatched;
  9.     }
  10. }

然后我们来试一试:


   
  1. @SneakyThrows
  2. @Test
  3. void jsonAnyGetter(){
  4.     MapUser mapUser = new MapUser();
  5.     mapUser.setName("felord.cn");
  6.     mapUser.setAge(22);
  7.     mapUser.setUnMatched(Collections.singletonMap("unknown","unknown"));
  8.     String json = objectMapper.writeValueAsString(mapUser);
  9.     // 获取json中unknown节点的值
  10.     Object read = JsonPath.parse(json)
  11.             .read(JsonPath.compile("$.unknown"));
  12.     Assertions.assertEquals("unknown",read);
  13. }

不过这个注解的使用也是有条件的:

  • 不能是静态方法。

  • 必须是无参方法。

  • 方法的返回值必须是java.util.Map

  • 一个实体中只能使用一个该注解。

@JsonAnySetter

正好和@JsonAnyGetter相反,这里就不介绍了。

@JsonAutoDetect

一般情况下,我们认为Jackson序列化对象的前提是有无参构造并且有Getter方法。事实上下面这个类依然可以序列化成json:


   
  1. @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
  2. public class ConstructUser {
  3.     private final String name;
  4.     private final Integer age;
  5.     public ConstructUser(String name, Integer age) {
  6.         this.name = name;
  7.         this.age = age;
  8.     }
  9. }

我们可以通过调整Java Bean中属性、getter方法、isGetter方法、setter方法、初始化实例的方法。可见级别可以分为:

  • DEFAULT: 需要根据上下文来判断,一般基于父类的可见性。

  • ANY:任何级别的都可以自动识别。

  • NONE:所有级别都不可以自动识别。

  • NON_PRIVATE:非private修饰的可以自动识别。

  • PROTECTED_AND_PUBLIC:被protectedpublic修饰的可以被自动识别。

  • PUBLIC_ONLY:只有被public修饰的才可以被自动识别。

@JsonBackReference

这个注解经常和另一个注解@JsonManagedReference成对出现,它为了解决递归的问题,例如两个类互相持有对方:


   
  1. Info info = new Info();
  2. Player player = new Player();
  3. player.setId(1);
  4. info.setPlayer(player);
  5. player.setInfo(info);
  6. // 直接无限递归了
  7. String InfiniteRecursionError = objectMapper.writeValueAsString(player);

json序列化的时候直接无限递归了。如果你想得到下面的序列化结果:


   
  1. // player
  2. {"id":1,"info":{"id":0}}

就需要在类PlayerInfo属性上标记@JsonManagedReference,同时在Info类中的Player属性上标记@JsonBackReference注解。

如果你想在序列化Player时直接忽略掉Info属性,即期望得到{"id":1},只需要在PlayerInfo属性上标记@JsonBackReference注解。

@JsonClassDescription

Jacksonjson schemas的支持,用来生成整个json的描述信息。

@JsonCreator

Jackson在反序列化时默认会去找Java Bean的无参构造,但是有些Bean没有无参构造,这时@JsonCreator就派上用场了。你可以将它标记在构造方法或静态工厂方法上,通常它还需要同@JsonProperty@JacksonInject配合,就像这样:


   
  1. @Getter
  2. public class DescriptionUser {
  3.     private final String name;
  4.     private final Integer age;
  5.     @JsonCreator
  6.     public DescriptionUser(@JsonProperty("name") String name, 
  7.                            @JsonProperty("age") Integer age) {
  8.         this.name = name;
  9.         this.age = age;
  10.     }
  11. }

对应的单元测试:


   
  1. @SneakyThrows
  2. @Test
  3. void jsonCreator() {
  4.     String json = "{\"name\": \"felord.cn\",\"age\": 22}";
  5.     DescriptionUser user = objectMapper.readValue(json, DescriptionUser.class);
  6.     Assertions.assertEquals("felord.cn", user.getName());
  7. }

你可以在静态初始化实例工厂方法上试试这个注解。

@JsonEnumDefaultValue

我们在定义性别枚举时往往只定义了两个性别。你不能指望用户守规矩。科学的方法是定义一个枚举用来兜底。就像这样:


   
  1. public enum Gender {
  2.     /**
  3.      * Female gender.
  4.      */
  5.     FEMALE,
  6.     /**
  7.      * Male gender.
  8.      */
  9.     MALE,
  10.     /**
  11.      * Unknown gender.
  12.      */
  13.     UNKNOWN
  14.    }

当用户乱填的时候都定义为未知。在jackson反序列化支持设置一个默认值来兜底。我们可以在Gender#UNKNOWN上标记@JsonEnumDefaultValue,然后反序列化:


   
  1. @SneakyThrows
  2. @Test
  3. void jsonEnumDefaultValue(){
  4.     // 开启未知枚举值使用默认值特性
  5.     objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
  6.     String maleJson = "{\"name\": \"felord.cn\",\"age\": 22,\"gender\":\"MALE\"}";
  7.     EnumUser male = objectMapper.readValue(maleJson, EnumUser.class);
  8.     Assertions.assertEquals(Gender.MALE,male.getGender());
  9.     String unknownJson = "{\"name\": \"felord.cn\",\"age\": 22,\"gender\":\"notClear\"}";
  10.     EnumUser unknownGender = objectMapper.readValue(unknownJson, EnumUser.class);
  11.     Assertions.assertEquals(Gender.UNKNOWN,unknownGender.getGender());
  12. }

注意:必须手动jackson开启未知枚举值使用默认值特性。

@JsonFilter

同一个实体类根据不同的场景可能需要不同的序列化策略。比如对于A用户实体的某些字段可见,对于B用户另一些字段可见,实现动态的数据字段权限。这种情况下,jackson中其它一些静态注解就很难实现,借助于@JsonFilter反而简单了,下面是实现方法:


   
  1. // 只序列化age的策略
  2. @JsonFilter("role_a")
  3. public class OnlyAge extends FilterUser{
  4. }
  5. // 不序列化age的策略 
  6. @JsonFilter("role_b")
  7. public class OnlyNameAndGender extends FilterUser{
  8. }

接下来定义role_arole_b的策略:


   
  1. @SneakyThrows
  2. @Test
  3. void jsonFilter() {
  4.     SimpleFilterProvider simpleFilterProvider = new SimpleFilterProvider();
  5.     // role_a只展示age  
  6.     SimpleBeanPropertyFilter onlyAgeFilter = SimpleBeanPropertyFilter.filterOutAllExcept("age");
  7.     // role_b只排除age
  8.     SimpleBeanPropertyFilter exceptAgeFilter = SimpleBeanPropertyFilter.serializeAllExcept("age");
  9.     simpleFilterProvider.addFilter("role_a", onlyAgeFilter);
  10.     simpleFilterProvider.addFilter("role_b", exceptAgeFilter);
  11.     objectMapper.setFilterProvider(simpleFilterProvider);
  12.     //被JsonFilter标记的类
  13.     OnlyAge onlyAgeUser = new OnlyAge();
  14.     onlyAgeUser.setName("felord.cn");
  15.     onlyAgeUser.setGender(Gender.MALE);
  16.     onlyAgeUser.setAge(22);
  17.     OnlyNameAndGender onlyNameAndGenderUser = new OnlyNameAndGender();
  18.     onlyNameAndGenderUser.setName("felord.cn");
  19.     onlyNameAndGenderUser.setGender(Gender.MALE);
  20.     onlyNameAndGenderUser.setAge(22);
  21.     String onlyAge = objectMapper.writeValueAsString(onlyAgeUser);
  22.     // 序列化的json中找不到name节点会抛出PathNotFoundException异常
  23.     Assertions.assertThrows(PathNotFoundException.class, () -> JsonPath.parse(onlyAge)
  24.             .read(JsonPath.compile("$.name")));
  25.     String onlyNameAndGender = objectMapper.writeValueAsString(onlyNameAndGenderUser);
  26.     // 序列化的json中找不到age节点会抛出PathNotFoundException异常
  27.     Assertions.assertThrows(PathNotFoundException.class, () -> JsonPath.parse(onlyNameAndGender)
  28.             .read(JsonPath.compile("$.age")));
  29. }

思考:结合AOP甚至是Spring Security是不是有搞头?

小结

Jackson是一款非常优秀的json类库,提供了丰富的注解来满足各种场景的需要。本篇介绍了一部分注解的用法和场景。胖哥也根据日常一些场景的需要结合这些注解设计了不少动态的、可扩展的、通用的序列化和反序列化功能,用起来非常方便顺手。只有掌握了技术才能运用技术,后续计划把剩下所有的注解都梳理出来分享给大家。另外keycloak的教程也在准备中,还请多多关注和支持。

Keycloak简单几步实现对Spring Boot应用的权限控制

2021-07-09

OAuth 2.0只是授权协议,OIDC才是认证授权协议

2021-07-12

文章来源: felord.blog.csdn.net,作者:码农小胖哥,版权归原作者所有,如需转载,请联系作者。

原文链接:felord.blog.csdn.net/article/details/118773191

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。