知識庫 / JSON / Jackson RSS 訂閱

使用 Jackson 中的 Optional

Data,Jackson
HongKong
10
09:57 PM · Dec 05 ,2025

 

1. 引言

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

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

2. 問題概述

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

2.1. Maven 依賴

為了使用 Jackson,請確保我們使用的是其最新版本:https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core

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

2.2. 我們的 Book 對象

然後,讓我們創建一個名為 Book 的類,該類包含一個普通字段和一個 可選 字段:

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);

我們將會看到,可選 字段的輸出不包含其值,而是包含一個嵌套的 JSON 對象,該對象包含一個名為 present 的字段:

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

儘管這看起來有些奇怪,但實際上這是我們應該期望的結果。

在這種情況下,isPresent()Optional 類中的一個公共 getter 方法。這意味着它將與 truefalse 值進行序列化,具體取決於它是否為空。這是 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 對象,我們會發現現在不再是嵌套的 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.