使用 Jackson 中的 Optional

Data,Jackson
Remote
0
12:20 AM · Dec 01 ,2025

 

1. 簡介在本文中,我們將對 Optional進行概述,然後解釋我們在使用它與 Jackson 時可能會遇到的問題。

之後,我們將介紹一種解決方案,讓 Jackson 將 Optionals 視為普通的空對象。

2. 方案概述

首先,讓我們看看嘗試序列化和反序列化 Optionals 與 Jackson 時會發生什麼。

2.1. Maven 依賴

要使用 Jackson,請確保我們正在使用 最新版本

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.17.2</version>
</dependency>

2.2. 我們的 Book 對象

然後,讓我們創建一個 Book 類,其中包含一個普通和 Optional 字段:

public class Book {
   String title;
   Optional<String> subTitle;
   
   // getters and setters omitted
}

請記住,Optionals 不應作為字段使用,我們這樣做是為了説明問題。

2.3. 序列化

現在,讓我們實例化一個 Book

Book book = new Book();
book.setTitle("Oliver Twist");
book.setSubTitle(Optional.of("The Parish Boy's Progress"));

最後,讓我們使用 Jackson 的 ObjectMapper 進行序列化:

String result = mapper.writeValueAsString(book);

我們會看到 Optional 字段的值不包含其值,而是包含一個嵌套的 JSON 對象,其中有一個名為 present 的字段:

{"title":"Oliver Twist","subTitle":{"present":true}}

雖然這看起來很奇怪,但實際上這是我們應該期望的結果。

在這種情況下,isPresent()Optional 類中的一個公共 getter。這意味着它將與 true 或 false 值進行序列化,具體取決於它是否為空。這是 Jackson 的默認序列化行為。

如果我們考慮一下,我們想要的是實際的 subtitle 字段的值進行序列化。

2.4. 反序列化

現在,讓我們反轉我們之前的示例,這次嘗試將對象反序列化為 Optional。我們會看到現在我們得到一個 JsonMappingException:

@Test(expected = JsonMappingException.class)
public void givenFieldWithValue_whenDeserializing_thenThrowException
    String bookJson = "{ \"title\": \"Oliver Twist\", \"subTitle\": \"foo\" }";
    Book result = mapper.readValue(bookJson, Book.class);
}

讓我們查看堆棧跟蹤:

com.fasterxml.jackson.databind.JsonMappingException:
  Can not construct instance of java.util.Optional:
  no String-argument constructor/factory method to deserialize from String value ('The Parish Boy's Progress')

這種行為再次有道理。本質上,Jackson 需要一個可以接受 subtitle 字段值的構造函數。而我們 Optional 字段沒有這樣的構造函數。

3. 解決方案

我們希望 Jackson 處理一個空 Optional 時將其視為 null ,並且當 Optional 存在值時,將其視為表示其值的字段。

幸運的是,這個問題已經為我們解決。 Jackson 擁有一組處理 JDK 8 數據類型的模塊,包括 Optional

3.1. Maven 依賴和註冊

首先,讓我們將最新版本作為 Maven 依賴項添加:

<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jdk8</artifactId>
   <version>2.13.3</version>
</dependency>

現在,我們只需要將模塊註冊到我們的 ObjectMapper 中:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());

3.2. 序列化

現在,讓我們測試一下。 如果我們再次嘗試序列化我們的 Book 對象,我們將看到現在有一個 subtitle, 而不是嵌套的 JSON:

Book book = new Book();
book.setTitle("Oliver Twist");
book.setSubTitle(Optional.of("The Parish Boy's Progress"));
String serializedBook = mapper.writeValueAsString(book);
 
assertThat(from(serializedBook).getString("subTitle"))
  .isEqualTo("The Parish Boy's Progress");

如果嘗試序列化一個空書,它將被存儲為 null

book.setSubTitle(Optional.empty());
String serializedBook = mapper.writeValueAsString(book);
 
assertThat(from(serializedBook).getString("subTitle")).isNull();

3.3. 反序列化

現在,讓我們重複反序列化的測試。 如果我們重新讀取我們的 Book,我們將看到我們不再得到 JsonMappingException:

Book newBook = mapper.readValue(result, Book.class);
 
assertThat(newBook.getSubTitle()).isEqualTo(Optional.of("The Parish Boy's Progress"));

最後,讓我們再次重複測試,這次使用 null. 我們將看到我們再次沒有得到 JsonMappingException, 並且實際上有一個空 Optional:

assertThat(newBook.getSubTitle()).isEqualTo(Optional.empty());

4. 結論

我們展示瞭如何通過利用 JDK 8 的 DataTypes 模塊來解決這個問題,證明了它使 Jackson 能夠將一個空 Optional 視為 null, 以及一個存在值 Optional 作為普通字段。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.