当前位置: 首页 > 科技观察

Jackson的注解的用法和场景,别看巨亏

时间:2023-03-12 21:58:42 科技观察

Jackson的注解列表今天总结一下Jackson的一系列注解的用法和场景,或许可以帮助大家实现一些功能。总结不易,请多多关注,点赞,转发。@JacksonAnnotation这个注解常用于Jackson自定义注解中,用来标记这是一个Jackson注解。这位胖哥在Jackson脱敏文章中用它实现了自定义序列化注解。@JacksonAnnotationsInside该注解用于标记Jackson复合注解,当你将多个Jackson注解组合成一个自定义注解时会用到。/***不为空忽略未知属性**/@Retention(RetentionPolicy.RUNTIME)@JacksonAnnotationsInside@JsonInclude(Include.NON_NULL)@JsonIgnoreProperties(ignoreUnknown=true)public@interfaceNotNullAndIgnoreAnnotation{}@JacksonInjectjson属性值会被反序列化当可以注入的时候,我们先在属性上标记:@DatapublicfinalclassJacksonInjectUser{@JacksonInject(value="dynamic")privateStringname;privateIntegerage;}然后name的值在反序列化的时候可以是动态的,不需要解析,拼字段。@SneakyThrows@TestvoidjacksonInject(){//这个值是动态的StringdynamicValue="someDynamicvalue";InjectableValues.StdinjectableValues=newInjectableValues.Std()//名字和注解中声明的一样.addValue("dynamic",dynamicValue);JacksonInjectUserjacksonInjectUser=objectMapper.setInjectableValues(injectableValues)//空json终于可以赋值了参数用于绑定策略控制。@JsonAlias用于在反序列化时命名JavaBean属性,可以绑定多个json键名。例如:@SneakyThrows@TestvoidjsonAlias(){//两个json的类型结构是一样的。可以定义一个Bean来接收StringuserJson="{\"name\":\"felord.cn\",\"age\":22}";StringitemJson="{\"category\":\"coco\",\"count\":50}";Domainuser=objectMapper.readValue(userJson,Domain.class);Assertions.assertEquals("felord.cn",user.getStr());Assertions.assertEquals(22,user.getNum());Domainitem=objectMapper.readValue(itemJson,Domain.class);Assertions.assertEquals("coco",item.getStr());Assertions.assertEquals(50,item.getNum());}@DatapublicclassDomain{@JsonAlias({"name","category"})privateStringstr;@JsonAlias({"age","count"})privateIntegernum;}注意:只能用于json反序列化。@JsonAnyGetter可以在json序列化过程中将Bean中的java.util.Map类型的属性“扁平化”。例如:一个JavaBean正常的json序列化结果是:{"name":"felord.cn","age":22,"unMatched":{"unknown":"unknown"}}但是我们需要:{"name":"felord.cn","age":22,"unknown":"unknown"}我们可以这样标记JavaBean:@DatapublicclassMapUser{privateStringname;privateIntegerage;privateMapunMatched;@JsonAnyGetterpublicMapgetUnMatched(){returnunMatched;}}那我们试试:@SneakyThrows@TestvoidjsonAnyGetter(){MapUsermapUser=newMapUser();mapUser.setName("felord.cn");mapUser.setAge(22);mapUser.setUnMatched(Collections.singletonMap("未知","未知"));Stringjson=objectMapper。writeValueAsString(mapUser);//获取json中未知节点的值Objectread=JsonPath.parse(json).read(JsonPath.compile("$.unknown"));Assertions.assertEquals("unknown",read);}但是这个注解的使用也是条件:不能是静态方法必须是无参方法。该方法的返回值必须是java.util.Map。每个实体只能使用一个注释。@JsonAnySetter与@JsonAnyGetter正好相反,这里就不介绍了。@JsonAutoDetect一般来说,我们认为Jackson序列化对象的前提是没有参数构造,有Getter方法。其实下面的类还是可以序列化成json的:@JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.ANY)publicclassConstructUser{privatefinalStringname;privatefinalIntegerage;publicConstructUser(Stringname,Integerage){this.name=name;this.age=age;}}我们可以调整JavaBean中的属性、getter方法、isGetter方法、setter方法、初始化实例的方法。可见性级别可以分为:DEFAULT:需要根据上下文判断,一般根据父类的可见性来判断。ANY:可以自动识别任何级别。NONE:没有一个级别可以被自动识别。NON_PRIVATE:非私有修饰的可以自动识别。PROTECTED_AND_PUBLIC:自动识别protected和public修饰的。PUBLIC_ONLY:只有被public修改的才能被自动识别。@JsonBackReference这个注解经常和另一个注解@JsonManagedReference成对出现。它解决了递归问题。例如,两个类相互保持:Infoinfo=newInfo();Playerplayer=newPlayer();player.setId(1);info.setPlayer(player);player.setInfo(info);//直接无限递归StringInfiniteRecursionError=objectMapper.writeValueAsString(player);json序列化的时候直接无限递归。如果想得到如下序列化结果://player{"id":1,"info":{"id":0}},需要在Player类的Info属性上标记@JsonManagedReference,在同时在Info类的Player属性上标记@JsonBackReference注解。如果想在序列化Player时直接忽略Info属性,即期望得到{"id":1},只需要在Player的Info属性上标注@JsonBackReference注解即可。@JsonClassDescriptionJackson对jsonschemas的支持,用于生成整个json的描述信息。@JsonCreatorJackson在反序列化时默认会寻找JavaBean的无参构造,但是有些bean没有无参构造,这时@JsonCreator就派上用场了。可以在构造函数或静态工厂方法上标记,通常还需要配合@JsonProperty或@JacksonInject,像这样:"name")Stringname,@JsonCreatorJsonProperty("age")Integerage){this.name=name;this.age=age;}}对应的单元测试:@SneakyThrows@TestvoidjsonCreator(){Stringjson="{\"name\":\"felord.cn\",\"age\":22}";DescriptionUseruser=objectMapper.readValue(json,DescriptionUser.class);Assertions.assertEquals("felord.cn",user.getName());}您可以静态初始化实例在工厂方法上尝试这个注解。@JsonEnumDefaultValue我们在定义性别枚举的时候,往往只定义了男性和女性两种性别。您不能指望用户会有所作为。科学的方法是定义一个枚举来覆盖底线。像这样:publicenumGender{/***Femalegender.*/FEMALE,/***Malegender.*/MALE,/***Unknowgenender.*/UNKNOWN}在用户随机填写时定义为未知。在jackson反序列化支持中设置默认值以覆盖底线。我们可以在Gender#UNKNOWN上标记@JsonEnumDefaultValue,然后反序列化:@SneakyThrows@TestvoidjsonEnumDefaultValue(){//启用未知枚举值使用默认值特征objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);StringmaleJson="{\"name\":\"felord.cn\",\"age\":22,\"gender\":\"MALE\"}";EnumUsermale=objectMapper.readValue(maleJson,EnumUser.class);Assertions.assertEquals(性别.MALE,male.getGender());StringunknownJson="{\"name\":\"felord.cn\",\"age\":22,\"gender\":\"notClear\"}";EnumUserunknownGender=objectMapper.readValue(unknownJson,EnumUser.class);Assertions.assertEquals(Gender.UNKNOWN,unknownGender.getGender());}注意:jackson必须手动启用未知枚举值才能使用默认值特性。@JsonFilter同一个实体类,根据不同的场景,可能需要不同的序列化策略。例如,用户实体A的部分字段可见,而用户B的其他字段可见,实现动态数据字段权限。在这种情况下,jackson中的其他一些静态注解就很难实现了。借助@JsonFilter,就简单了。下面是实现方法:@JsonFilter("role_a")publicclassOnlyAgeextendsFilterUser{}//不序列化年龄的策略@JsonFilter("role_b")publicclassOnlyNameAndGenderextendsFilterUser{}接下来来定义role_a和role_b的策略:@SneakyThrows@TestvoidjsonFilter(){SimpleFilterProvidersimpleFilterProvider=newSimpleFilterProvider();//role_a只展示ageSimpleBeanPropertyFilteronlyAgeFilter=SimpleBeanPropertyFilter.filterOutAllExcept("age");//role_b只排除ageSimpleBeanPropertyFilterexceptAgeFilter=SimpleBeanPropertyFilter.serializeAllExcept("age");simpleFilterProvider.addFilter("role_a",onlyAgeFilter);simpleFilterProvider.addFilter("role_b",exceptAgeFilter);objectMapper.setFilterProvider(simpleFilterProvider);//被JsonFilter标记的类OnlyAgeonlyAgeUser=newOnlyAge();onlyAgeUser.setName("felord.cn");onlyAgeUser.setGender(Gender.MALE);onlyAgeUser.setAge(22);OnlyNameAndGenderonlyNameAndGenderUser=newOnlyNameAndGender();onlyNameAndGenderUser.setName("felord.cn");onlyNameAndGenderUser.setGender(Gender.MALE);onlyNameAndGenderUser.setAge(22);StringonlyAge=objectMapper.writeValueAsString(onlyAgeUser);//在序列化的json中找不到name节点会抛出PathNotFoundExceptionAssertions.assertThrows(PathNotFoundException.class,()->JsonPath.parse(onlyAge).read(JsonPath.compile("$.name")));StringonlyNameAndGender=objectMapper.writeValueAsString(onlyNameAndGenderUser);//如果序列化后的json中找不到age节点,会抛出PathNotFoundException。Assertions.assertThrows(PathNotFoundException.class,()->JsonPath.parse(onlyNameAndGender).read(JsonPath.compile("$.age")));}思考:结合AOP甚至SpringSecurity是个好主意吗?总结Jackson是一个非常优秀的json库,提供了丰富的注解来满足各种场景的需求。本文介绍一些注解的使用场景。胖哥还根据一些日常场景的需要结合这些注解设计了很多动态的、可扩展的、通用的序列化反序列化函数,使用起来非常方便。只有掌握了技术,我们才能使用技术。后续计划会把剩下的注释全部整理出来分享给大家。另外,keycloak教程也在准备中,请大家多多关注和支持。本文转载自微信公众号“码农小胖哥”,可通过以下二维码关注。转载本文请联系码农小胖公众号。