1. 程式人生 > >除了FastJson,你也應該瞭解一下Jackson(二)

除了FastJson,你也應該瞭解一下Jackson(二)

## 概覽 上一篇文章介紹了Jackson中的對映器ObjectMapper,以及如何使用它來實現Json與Java物件之間的序列化和反序列化,最後介紹了Jackson中一些序列化/反序列化的高階特性。而本文將會介紹Jackson中的一些常用的(序列化/反序列化)註解,並且通過示例來演示如何使用這些註解,從而來提高我們在處理Json上的工作效率。 --- ## 序列化註解 ### @JsonAnyGetter @JsonAnyGetter註解允許靈活地使用對映(鍵值對,如Map)欄位作為標準屬性。 我們宣告如下Java類: ```java @Data @Accessors(chain = true) public static class ExtendableBean { public String name; private Map properties; @JsonAnyGetter public Map getProperties() { return properties; } } ``` 編寫測試程式碼,測試@JsonAnyGetter: ```java @Test public void testJsonAnyGetter() throws JsonProcessingException { ExtendableBean extendableBean = new ExtendableBean(); Map map = new HashMap<>(); map.put("age", "13"); extendableBean.setName("dxsn").setProperties(map); log.info(new ObjectMapper().writeValueAsString(extendableBean)); //列印:{"name":"dxsn","age":"13"} assertThat(new ObjectMapper().writeValueAsString(extendableBean)).contains("name"); assertThat(new ObjectMapper().writeValueAsString(extendableBean)).contains("age"); } ``` 如上,可以看properties屬性中的鍵值對(Map)被擴充套件到了ExtendableBean的Json物件中。 ### @JsonGetter @JsonGetter註解是@JsonProperty註解的替代品,用來將一個方法標記為getter方法。 我們建立以下Java類 ```java @Data @AllArgsConstructor @NoArgsConstructor public static class MyBean { public int id; private String name; @JsonGetter("name") public String getTheName() { return name; } } ``` 如上,我們在類中聲明瞭一個getTheName()方法,並且使用@JsonGetter("name")修飾,此時,該方法將會被Jackson認作是name屬性的get方法。 編寫測試程式碼: ```java @Test public void testJsonGetter() throws JsonProcessingException { MyBean myBean = new MyBean(1, "dxsn"); String jsonStr = new ObjectMapper().writeValueAsString(myBean); log.info(jsonStr); assertThat(jsonStr).contains("id"); assertThat(jsonStr).contains("name"); } ``` 可以看到,jackson將私有屬性name,也進行了序列化。 ### @JsonPropertyOrder 我們可以使用@JsonPropertyOrder註解來指定Java物件的屬性序列化順序。 ```java @JsonPropertyOrder({"name", "id"}) //order by key's name //@JsonPropertyOrder(alphabetic = true) @Data @Accessors(chain = true) public static class MyOrderBean { public int id; public String name; } ``` 編寫測試程式碼: ```java @Test public void testJsonPropertyOrder1() throws JsonProcessingException { MyOrderBean myOrderBean = new MyOrderBean().setId(1).setName("dxsn"); String jsonStr = new ObjectMapper().writeValueAsString(myOrderBean); log.info(jsonStr); assertThat(jsonStr).isEqualTo("{\"name\":\"dxsn\",\"id\":1}"); } ``` 如上,可以看到序列化得到的Json物件中屬性的排列順序正是我們在註解中指定的順序。 ### @JsonRawValue @JsonRawValue註解可以指示Jackson按原樣序列化屬性。 在下面的例子中,我們使用@JsonRawValue嵌入一些定製的JSON作為一個實體的值: ```java @Data @AllArgsConstructor @NoArgsConstructor public static class RawBean { public String name; @JsonRawValue public String json; } ``` 編寫測試程式碼: ```java @Test public void testJsonRawValue() throws JsonProcessingException { RawBean rawBean = new RawBean("dxsn", "{\"love\":\"true\"}"); log.info(new ObjectMapper().writeValueAsString(rawBean)); //輸出:{"name":"dxsn","json":{"love":"true"}} String result = new ObjectMapper().writeValueAsString(rawBean); assertThat(result).contains("dxsn"); assertThat(result).contains("{\"love\":\"true\"}"); } ``` ### @JsonValue @JsonValue表示Jackson將使用一個方法來序列化整個例項。 下面我們建立一個列舉類: ```java @AllArgsConstructor public static enum TypeEnumWithValue { TYPE1(1, "Type A"), TYPE2(2, "Type 2"); private Integer id; private String name; @JsonValue public String getName() { return name; } } ``` 如上,我們在getName()上使用@JsonValue進行修飾。 編寫測試程式碼: ```java @Test public void testJsonValue() throws JsonProcessingException { String jsonStr = new ObjectMapper().writeValueAsString(TypeEnumWithValue.TYPE2); log.info(jsonStr); assertThat(jsonStr).isEqualTo("Type 2"); } ``` 可以看到,列舉類的物件序列化後的值即getName()方法的返回值。 ### @JsonRootName 如果啟用了包裝(wrapping),則使用@JsonRootName註解可以指定要使用的根包裝器的名稱。 下面我們建立一個使用@JsonRootName修飾的Java類: ```java @JsonRootName(value = "user") @Data @AllArgsConstructor public static class UserWithRoot { public int id; public String name; } ``` 編寫測試: ```java @Test public void testJsonRootName() throws JsonProcessingException { UserWithRoot userWithRoot = new UserWithRoot(1, "dxsn"); ObjectMapper mapper = new ObjectMapper(); //⬇️重點!!! mapper.enable(SerializationFeature.WRAP_ROOT_VALUE); String result = mapper.writeValueAsString(userWithRoot); log.info(result); //輸出:{"user":{"id":1,"name":"dxsn"}} assertThat(result).contains("dxsn"); assertThat(result).contains("user"); } ``` 上面程式碼中,我們通過開啟ObjectMapper的SerializationFeature.WRAP_ROOT_VALUE。可以看到UserWithRoot物件被序列化後的Json物件被包裝在user中,而非單純的`{"id":1,"name":"dxsn"}`。 ### @JsonSerialize @JsonSerialize註解表示序列化實體時要使用的自定義序列化器。 我們定義一個自定義的序列化器: ```java public static class CustomDateSerializer extends StdSerializer { private static SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); public CustomDateSerializer() { this(null); } public CustomDateSerializer(Class t) { super(t); } @Override public void serialize( Date value, JsonGenerator gen, SerializerProvider arg2) throws IOException, JsonProcessingException { gen.writeString(formatter.format(value)); } } ``` 使用自定義的序列化器,建立Java類: ```java @Data @AllArgsConstructor public static class Event { public String name; @JsonSerialize(using = CustomDateSerializer.class) public Date eventDate; } ``` 編寫測試程式碼: ```java @Test public void testJsonSerialize() throws ParseException, JsonProcessingException { SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); String toParse = "20-12-2014 02:30:00"; Date date = formatter.parse(toParse); Event event = new Event("party", date); String result = new ObjectMapper().writeValueAsString(event); assertThat(result).contains(toParse); } ``` 可以看到,使用@JsonSerialize註解修飾指定屬性後,將會使用指定的序列化器來序列化該屬性。 --- ## 反序列化註解 ### @JsonCreator 我們可以使用@JsonCreator註解來優化/替換反序列化中使用的構造器/工廠。 當我們需要反序列化一些與我們需要獲取的目標實體不完全匹配的JSON時,它非常有用。 現在,有如下一個Json物件: ```json {"id":1,"theName":"My bean"} ``` 我們聲名了一個Java類: ```java @Data public static class BeanWithCreator { private int id; private String name; } ``` 此時,在我們的目標實體中沒有theName欄位,只有name欄位。現在,我們不想改變實體本身,此時可以通過使用@JsonCreator和@JsonProperty註解來修飾建構函式: ```java @Data public static class BeanWithCreator { private int id; private String name; @JsonCreator public BeanWithCreator(@JsonProperty("id") int id, @JsonProperty("theName") String name) { this.id = id; this.name = name; } } ``` 編寫測試: ```java @Test public void beanWithCreatorTest() throws JsonProcessingException { String str = "{\"id\":1,\"theName\":\"My bean\"}"; BeanWithCreator bean = new ObjectMapper() .readerFor(BeanWithCreator.class) .readValue(str); assertThat(bean.getId()).isEqualTo(1); assertThat(bean.getName()).isEqualTo("My bean"); } ``` 可以看到,即使Json物件中的欄位名和實體類中不一樣,但由於我們手動指定了對映欄位的名字,從而反序列化成功。 ### @JacksonInject @JacksonInject表示java物件中的屬性將通過注入來賦值,而不是從JSON資料中獲得其值。 建立如下實體類,其中有欄位被@JacksonInject修飾: ```java public static class BeanWithInject { @JacksonInject public int id; public String name; } ``` 編寫測試: ```java @Test public void jacksonInjectTest() throws JsonProcessingException { String json = "{\"name\":\"dxsn\"}"; InjectableValues inject = new InjectableValues.Std() .addValue(int.class, 1); BeanWithInject bean = new ObjectMapper().reader(inject) .forType(BeanWithInject.class) .readValue(json); assertThat(bean.id).isEqualTo(1); assertThat(bean.name).isEqualTo("dxsn"); } ``` 如上,我們在測試中將json字串(僅存在name欄位)進行反序列化,其中id通過注入的方式對屬性進行賦值。 ### @JsonAnySetter @JsonAnySetter允許我們靈活地使用對映(鍵值對、Map)作為標準屬性。在反序列化時,JSON的屬性將被新增到對映中。 建立一個帶有@JsonAnySetter的實體類: ```java public static class ExtendableBean { public String name; public Map properties; @JsonAnySetter public void add(String key, String value) { if (properties == null) { properties = new HashMap<>(); } properties.put(key, value); } } ``` 編寫測試: ```java @Test public void testJsonAnySetter() throws JsonProcessingException { String json = "{\"name\":\"dxsn\", \"attr2\":\"val2\", \"attr1\":\"val1\"}"; ExtendableBean extendableBean = new ObjectMapper().readerFor(ExtendableBean.class).readValue(json); assertThat(extendableBean.name).isEqualTo("dxsn"); assertThat(extendableBean.properties.size()).isEqualTo(2); } ``` 可以看到,json物件中的attr1,attr2屬性在反序列化之後進入了properties。 ### @JsonSetter @JsonSetter是@JsonProperty的替代方法,它將方法標記為屬性的setter方法。 當我們需要讀取一些JSON資料,但目標實體類與該資料不完全匹配時,這非常有用,因此我們需要優化使其適合該資料。 建立如下實體類: ```java @Data public static class MyBean { public int id; private String name; @JsonSetter("name") public void setTheName(String name) { this.name = "hello " + name; } } ``` 編寫測試: ```java @Test public void testJsonSetter() throws JsonProcessingException { String json = "{\"id\":1,\"name\":\"dxsn\"}"; MyBean bean = new ObjectMapper().readerFor(MyBean.class).readValue(json); assertThat(bean.getName()).isEqualTo("hello dxsn"); } ``` 可以看到,json物件中的name屬性為“dxsn”,我們通過在MyBean類中定義了使用@JsonSetter("name")註解修飾的方法,這表明該類的物件在反序列話的時候,name屬性將來自此方法。最後MyBean物件中name的值變為了hello dxsn。 ### @JsonDeserialize @JsonDeserialize註解指定了在反序列化的時候使用的反序列化器。 如下,定義了一個自定義的反序列化器: ```java public static class CustomDateDeserializer extends StdDeserializer { private static SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); public CustomDateDeserializer() { this(null); } public CustomDateDeserializer(Class vc) { super(vc); } @Override public Date deserialize(JsonParser jsonparser, DeserializationContext context) throws IOException { String date = jsonparser.getText(); try { return formatter.parse(date); } catch (ParseException e) { throw new RuntimeException(e); } } } ``` 建立一個使用@JsonDeserialize(using = CustomDateDeserializer.class)修飾的實體類: ```java public static class Event { public String name; @JsonDeserialize(using = CustomDateDeserializer.class) public Date eventDate; } ``` 編寫測試: ```java @Test public void whenDeserializingUsingJsonDeserialize_thenCorrect() throws IOException { String json = "{\"name\":\"party\",\"eventDate\":\"20-12-2014 02:30:00\"}"; SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); Event event = new ObjectMapper().readerFor(Event.class).readValue(json); assertThat(event.name).isEqualTo("party"); assertThat(event.eventDate).isEqualTo(df.format(event.eventDate)); } ``` 可以看到,在Event物件中,eventDate屬性通過自定義的反序列化器,將“20-12-2014 02:30:00”反序列化成了Date物件。 ### @JsonAlias @JsonAlias在反序列化期間為屬性定義一個或多個替代名稱。讓我們通過一個簡單的例子來看看這個註解是如何工作的: ```java @Data public static class AliasBean { @JsonAlias({"fName", "f_name"}) private String firstName; private String lastName; } ``` 如上,我們編寫了一個使用@JsonAlias修飾的AliasBean實體類。 編寫測試: ```java @Test public void whenDeserializingUsingJsonAlias_thenCorrect() throws IOException { String json = "{\"fName\": \"John\", \"lastName\": \"Green\"}"; AliasBean aliasBean = new ObjectMapper().readerFor(AliasBean.class).readValue(json); assertThat(aliasBean.getFirstName()).isEqualTo("John"); } ``` 可以看到,即使json物件中的欄位名是fName,但是由於在AliasBean中使用@JsonAlias修飾了firstName屬性,並且指定了兩個別名。所以反序列化之後fName被對映到AliasBean物件的firstName屬性上。 --- ## 更多 除上述註解之外,Jackson還提供了很多額外的註解,這裡不一一列舉,接下來會例舉幾個常用的註解: - **@JsonProperty**:可以在類的指定屬性上新增@JsonProperty註解來表示其對應在JSON中的屬性名。 - **@JsonFormat**:此註解在序列化物件中的日期/時間型別屬性時可以指定一種字串格式輸出,如:@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = “dd-MM-yyyy hh:mm:ss”)。 - **@JsonUnwrapped**:@JsonUnwrapped定義了在序列化/反序列化時應該被扁平化的值。 - **@JsonIgnore**:序列化/反序列化時忽略被修飾的屬性。 - ...... --- ## 總結 本文主要介紹了Jackson常用的序列化/反序列化註解,最後介紹了幾個常用的通用註解。Jackson中提供的註解除了本文列舉的還有很多很多,使用註解可以讓我們的序列化/反序列化工作更加輕鬆。如果你想將某庫換成Jackson,希望這篇文章可以幫到你。 本文涉及的程式碼地址:https://gitee.com/jeker8chen/jackson-annotation-demo ---