概覽
上一篇文章介紹了Jackson中的對映器ObjectMapper,以及如何使用它來實現Json與Java物件之間的序列化和反序列化,最後介紹了Jackson中一些序列化/反序列化的高階特性。而本文將會介紹Jackson中的一些常用的(序列化/反序列化)註解,並且通過示例來演示如何使用這些註解,從而來提高我們在處理Json上的工作效率。
序列化註解
@JsonAnyGetter
@JsonAnyGetter註解允許靈活地使用對映(鍵值對,如Map)欄位作為標準屬性。
我們宣告如下Java類:
@Data
@Accessors(chain = true)
public static class ExtendableBean {
public String name;
private Map<String, String> properties;
@JsonAnyGetter
public Map<String, String> getProperties() {
return properties;
}
}
編寫測試程式碼,測試@JsonAnyGetter:
@Test
public void testJsonAnyGetter() throws JsonProcessingException {
ExtendableBean extendableBean = new ExtendableBean();
Map<String, String> 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類
@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方法。
編寫測試程式碼:
@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物件的屬性序列化順序。
@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;
}
編寫測試程式碼:
@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作為一個實體的值:
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class RawBean {
public String name;
@JsonRawValue
public String json;
}
編寫測試程式碼:
@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將使用一個方法來序列化整個例項。
下面我們建立一個列舉類:
@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進行修飾。
編寫測試程式碼:
@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類:
@JsonRootName(value = "user")
@Data
@AllArgsConstructor
public static class UserWithRoot {
public int id;
public String name;
}
編寫測試:
@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註解表示序列化實體時要使用的自定義序列化器。
我們定義一個自定義的序列化器:
public static class CustomDateSerializer extends StdSerializer<Date> {
private static SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
public CustomDateSerializer() {
this(null);
}
public CustomDateSerializer(Class<Date> t) {
super(t);
}
@Override
public void serialize(
Date value, JsonGenerator gen, SerializerProvider arg2) throws IOException, JsonProcessingException {
gen.writeString(formatter.format(value));
}
}
使用自定義的序列化器,建立Java類:
@Data
@AllArgsConstructor
public static class Event {
public String name;
@JsonSerialize(using = CustomDateSerializer.class)
public Date eventDate;
}
編寫測試程式碼:
@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物件:
{"id":1,"theName":"My bean"}
我們聲名了一個Java類:
@Data
public static class BeanWithCreator {
private int id;
private String name;
}
此時,在我們的目標實體中沒有theName欄位,只有name欄位。現在,我們不想改變實體本身,此時可以通過使用@JsonCreator和@JsonProperty註解來修飾建構函式:
@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;
}
}
編寫測試:
@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修飾:
public static class BeanWithInject {
@JacksonInject
public int id;
public String name;
}
編寫測試:
@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的實體類:
public static class ExtendableBean {
public String name;
public Map<String, String> properties;
@JsonAnySetter
public void add(String key, String value) {
if (properties == null) {
properties = new HashMap<>();
}
properties.put(key, value);
}
}
編寫測試:
@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資料,但目標實體類與該資料不完全匹配時,這非常有用,因此我們需要優化使其適合該資料。
建立如下實體類:
@Data
public static class MyBean {
public int id;
private String name;
@JsonSetter("name")
public void setTheName(String name) {
this.name = "hello " + name;
}
}
編寫測試:
@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註解指定了在反序列化的時候使用的反序列化器。
如下,定義了一個自定義的反序列化器:
public static class CustomDateDeserializer extends StdDeserializer<Date> {
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)修飾的實體類:
public static class Event {
public String name;
@JsonDeserialize(using = CustomDateDeserializer.class)
public Date eventDate;
}
編寫測試:
@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在反序列化期間為屬性定義一個或多個替代名稱。讓我們通過一個簡單的例子來看看這個註解是如何工作的:
@Data
public static class AliasBean {
@JsonAlias({"fName", "f_name"})
private String firstName;
private String lastName;
}
如上,我們編寫了一個使用@JsonAlias修飾的AliasBean實體類。
編寫測試:
@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
??????????????????
歡迎訪問筆者部落格:blog.dongxishaonian.tech
關注筆者公眾號,推送各類原創/優質技術文章 ⬇️