Jackson注解的用法和場景,不看巨虧
Jackson注解一覽
今天總結(jié)一下Jackson的一系列注解的用法和場景,或許能幫助你實現(xiàn)一些功能,總結(jié)不易,還請多多關(guān)注、點贊、轉(zhuǎn)發(fā)。
@JacksonAnnotation
這個注解經(jīng)常用于Jackson自定義注解中,用來標記這是一個Jackson注解,這個胖哥在Jackson脫敏一文中用過它來實現(xiàn)自定義的序列化注解。
@JacksonAnnotationsInside
這個注解用來標記Jackson復合注解,當你使用多個Jackson注解組合成一個自定義注解時會用到它。
- /**
- * 非空以及忽略未知屬性
- **/
- @Retention(RetentionPolicy.RUNTIME)
- @JacksonAnnotationsInside
- @JsonInclude(Include.NON_NULL)
- @JsonIgnoreProperties(ignoreUnknown = true)
- public @interface NotNullAndIgnoreAnnotation {}
@JacksonInject
json屬性值將在反序列化時可以被注入,我們先在屬性上標記:
- @Data
- public final class JacksonInjectUser {
- @JacksonInject(value = "dynamic")
- private String name;
- private Integer age;
- }
然后name的值就可以在反序列化的時候動態(tài)化,不再需要去解析、拼字段。
- @SneakyThrows
- @Test
- void jacksonInject() {
- // 這個值動態(tài)化了
- String dynamicValue = "some Dynamic value";
- InjectableValues.Std injectableValues = new InjectableValues.Std()
- // 名稱和注解中聲明的相同才行
- .addValue("dynamic", dynamicValue);
- JacksonInjectUser jacksonInjectUser = objectMapper.setInjectableValues(injectableValues)
- // 空json 最后居然可以賦值
- .readValue("{}", JacksonInjectUser.class);
- Assertions.assertEquals(dynamicValue,jacksonInjectUser.getName());
- }
注意:@JacksonInject中提供了useInput參數(shù)進行綁定策略控制。
@JsonAlias
在反序列化的時候來對Java Bean的屬性進行名稱綁定,可以綁定多個json的鍵名。舉個例子:
- @SneakyThrows
- @Test
- void jsonAlias(){
- // 兩個json的類型結(jié)構(gòu)是相同的 可以定義一個Bean來接收
- String userJson = "{\"name\": \"felord.cn\",\"age\": 22}";
- String itemJson = "{\"category\": \"coco\", \"count\": 50 }";
- Domain user = objectMapper.readValue(userJson, Domain.class);
- Assertions.assertEquals("felord.cn",user.getStr());
- Assertions.assertEquals(22,user.getNum());
- Domain item = objectMapper.readValue(itemJson, Domain.class);
- Assertions.assertEquals("coco",item.getStr());
- Assertions.assertEquals(50,item.getNum());
- }
- @Data
- public class Domain{
- @JsonAlias({"name","category"})
- private String str;
- @JsonAlias({"age","count"})
- private Integer num;
- }
注意:只能用于json反序列化。
@JsonAnyGetter
在json序列化時可以將Bean中的java.util.Map類型的屬性“平鋪展開”,舉個例子:
某個Java Bean正常的json序列化結(jié)果是:
- {
- "name": "felord.cn",
- "age": 22,
- "unMatched": {
- "unknown": "unknown"
- }
- }
但是我們需要:
- {
- "name": "felord.cn",
- "age": 22,
- "unknown": "unknown"
- }
我們可以對Java Bean這么標記:
- @Data
- public class MapUser {
- private String name;
- private Integer age;
- private Map<String,Object> unMatched;
- @JsonAnyGetter
- public Map<String, Object> getUnMatched() {
- return unMatched;
- }
- }
然后我們來試一試:
- @SneakyThrows
- @Test
- void jsonAnyGetter(){
- MapUser mapUser = new MapUser();
- mapUser.setName("felord.cn");
- mapUser.setAge(22);
- mapUser.setUnMatched(Collections.singletonMap("unknown","unknown"));
- String json = objectMapper.writeValueAsString(mapUser);
- // 獲取json中unknown節(jié)點的值
- Object read = JsonPath.parse(json)
- .read(JsonPath.compile("$.unknown"));
- Assertions.assertEquals("unknown",read);
- }
不過這個注解的使用也是有條件的:
- 不能是靜態(tài)方法。
- 必須是無參方法。
- 方法的返回值必須是java.util.Map。
- 一個實體中只能使用一個該注解。
@JsonAnySetter
正好和@JsonAnyGetter相反,這里就不介紹了。
@JsonAutoDetect
一般情況下,我們認為Jackson序列化對象的前提是有無參構(gòu)造并且有Getter方法。事實上下面這個類依然可以序列化成json:
- @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
- public class ConstructUser {
- private final String name;
- private final Integer age;
- public ConstructUser(String name, Integer age) {
- this.name = name;
- this.age = age;
- }
- }
我們可以通過調(diào)整Java Bean中屬性、getter方法、isGetter方法、setter方法、初始化實例的方法。可見級別可以分為:
- DEFAULT: 需要根據(jù)上下文來判斷,一般基于父類的可見性。
- ANY:任何級別的都可以自動識別。
- NONE:所有級別都不可以自動識別。
- NON_PRIVATE:非private修飾的可以自動識別。
- PROTECTED_AND_PUBLIC:被protected和public修飾的可以被自動識別。
- PUBLIC_ONLY:只有被public修飾的才可以被自動識別。
@JsonBackReference
這個注解經(jīng)常和另一個注解@JsonManagedReference成對出現(xiàn),它為了解決遞歸的問題,例如兩個類互相持有對方:
- Info info = new Info();
- Player player = new Player();
- player.setId(1);
- info.setPlayer(player);
- player.setInfo(info);
- // 直接無限遞歸了
- String InfiniteRecursionError = objectMapper.writeValueAsString(player);
json序列化的時候直接無限遞歸了。如果你想得到下面的序列化結(jié)果:
- // player
- {"id":1,"info":{"id":0}}
就需要在類Player的Info屬性上標記@JsonManagedReference,同時在Info類中的Player屬性上標記@JsonBackReference注解。
如果你想在序列化Player時直接忽略掉Info屬性,即期望得到{"id":1},只需要在Player的Info屬性上標記@JsonBackReference注解。
@JsonClassDescription
Jackson對json schemas的支持,用來生成整個json的描述信息。
@JsonCreator
Jackson在反序列化時默認會去找Java Bean的無參構(gòu)造,但是有些Bean沒有無參構(gòu)造,這時@JsonCreator就派上用場了。你可以將它標記在構(gòu)造方法或靜態(tài)工廠方法上,通常它還需要同@JsonProperty或@JacksonInject配合,就像這樣:
- @Getter
- public class DescriptionUser {
- private final String name;
- private final Integer age;
- @JsonCreator
- public DescriptionUser(@JsonProperty("name") String name,
- @JsonProperty("age") Integer age) {
- this.name = name;
- this.age = age;
- }
- }
對應的單元測試:
- @SneakyThrows
- @Test
- void jsonCreator() {
- String json = "{\"name\": \"felord.cn\",\"age\": 22}";
- DescriptionUser user = objectMapper.readValue(json, DescriptionUser.class);
- Assertions.assertEquals("felord.cn", user.getName());
- }
你可以在靜態(tài)初始化實例工廠方法上試試這個注解。
@JsonEnumDefaultValue
我們在定義性別枚舉時往往只定義了男和女兩個性別。你不能指望用戶守規(guī)矩。科學的方法是定義一個枚舉用來兜底。就像這樣:
- public enum Gender {
- /**
- * Female gender.
- */
- FEMALE,
- /**
- * Male gender.
- */
- MALE,
- /**
- * Unknown gender.
- */
- UNKNOWN
- }
當用戶亂填的時候都定義為未知。在jackson反序列化支持設(shè)置一個默認值來兜底。我們可以在Gender#UNKNOWN上標記@JsonEnumDefaultValue,然后反序列化:
- @SneakyThrows
- @Test
- void jsonEnumDefaultValue(){
- // 開啟未知枚舉值使用默認值特性
- objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
- String maleJson = "{\"name\": \"felord.cn\",\"age\": 22,\"gender\":\"MALE\"}";
- EnumUser male = objectMapper.readValue(maleJson, EnumUser.class);
- Assertions.assertEquals(Gender.MALE,male.getGender());
- String unknownJson = "{\"name\": \"felord.cn\",\"age\": 22,\"gender\":\"notClear\"}";
- EnumUser unknownGender = objectMapper.readValue(unknownJson, EnumUser.class);
- Assertions.assertEquals(Gender.UNKNOWN,unknownGender.getGender());
- }
注意:必須手動jackson開啟未知枚舉值使用默認值特性。
@JsonFilter
同一個實體類根據(jù)不同的場景可能需要不同的序列化策略。比如對于A用戶實體的某些字段可見,對于B用戶另一些字段可見,實現(xiàn)動態(tài)的數(shù)據(jù)字段權(quán)限。這種情況下,jackson中其它一些靜態(tài)注解就很難實現(xiàn),借助于@JsonFilter反而簡單了,下面是實現(xiàn)方法:
- @JsonFilter("role_a")
- public class OnlyAge extends FilterUser{
- }
- // 不序列化age的策略
- @JsonFilter("role_b")
- public class OnlyNameAndGender extends FilterUser{
- }
接下來定義role_a和role_b的策略:
- @SneakyThrows
- @Test
- void jsonFilter() {
- SimpleFilterProvider simpleFilterProvider = new SimpleFilterProvider();
- // role_a只展示age
- SimpleBeanPropertyFilter onlyAgeFilter = SimpleBeanPropertyFilter.filterOutAllExcept("age");
- // role_b只排除age
- SimpleBeanPropertyFilter exceptAgeFilter = SimpleBeanPropertyFilter.serializeAllExcept("age");
- simpleFilterProvider.addFilter("role_a", onlyAgeFilter);
- simpleFilterProvider.addFilter("role_b", exceptAgeFilter);
- objectMapper.setFilterProvider(simpleFilterProvider);
- //被JsonFilter標記的類
- OnlyAge onlyAgeUser = new OnlyAge();
- onlyAgeUser.setName("felord.cn");
- onlyAgeUser.setGender(Gender.MALE);
- onlyAgeUser.setAge(22);
- OnlyNameAndGender onlyNameAndGenderUser = new OnlyNameAndGender();
- onlyNameAndGenderUser.setName("felord.cn");
- onlyNameAndGenderUser.setGender(Gender.MALE);
- onlyNameAndGenderUser.setAge(22);
- String onlyAge = objectMapper.writeValueAsString(onlyAgeUser);
- // 序列化的json中找不到name節(jié)點會拋出PathNotFoundException異常
- Assertions.assertThrows(PathNotFoundException.class, () -> JsonPath.parse(onlyAge)
- .read(JsonPath.compile("$.name")));
- String onlyNameAndGender = objectMapper.writeValueAsString(onlyNameAndGenderUser);
- // 序列化的json中找不到age節(jié)點會拋出PathNotFoundException異常
- Assertions.assertThrows(PathNotFoundException.class, () -> JsonPath.parse(onlyNameAndGender)
- .read(JsonPath.compile("$.age")));
- }
思考:結(jié)合AOP甚至是Spring Security是不是有搞頭?
小結(jié)
Jackson是一款非常優(yōu)秀的json類庫,提供了豐富的注解來滿足各種場景的需要。本篇介紹了一部分注解的用法和場景。胖哥也根據(jù)日常一些場景的需要結(jié)合這些注解設(shè)計了不少動態(tài)的、可擴展的、通用的序列化和反序列化功能,用起來非常方便順手。只有掌握了技術(shù)才能運用技術(shù),后續(xù)計劃把剩下所有的注解都梳理出來分享給大家。另外keycloak的教程也在準備中,還請多多關(guān)注和支持。
本文轉(zhuǎn)載自微信公眾號「碼農(nóng)小胖哥」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系碼農(nóng)小胖哥公眾號。