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

東溪陳姓少年發表於2020-06-09

概覽

上一篇文章介紹了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

關注筆者公眾號,推送各類原創/優質技術文章 ⬇️

WechatIMG6

相關文章