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
















 
 
 










 
 
 
 