ジャクソンでオプションを使用する
1. 序章
この記事では、オプションクラスの概要を説明した後、Jacksonで使用するときに発生する可能性のあるいくつかの問題について説明します。
これに続いて、JacksonにOptionsを通常のnull許容オブジェクトであるかのように処理させるソリューションを紹介します。
2. 問題の概要
まず、Jacksonでオプションをシリアル化および逆シリアル化しようとするとどうなるかを見てみましょう。
2.1. Mavenの依存関係
ジャクソンを使用するには、最新バージョンを使用していることを確認しましょう。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.3</version>
</dependency>
2.2. 私たちの本のオブジェクト
次に、1つの通常フィールドと1つのオプションフィールドを含むクラスBookを作成しましょう。
public class Book {
String title;
Optional<String> subTitle;
// getters and setters omitted
}
オプションをフィールドとして使用しないでください。これは、問題を説明するために行っています。
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);
オプションフィールドの出力にはその値が含まれていませんが、代わりにpresentというフィールドを持つネストされたJSONオブジェクトが含まれていることがわかります。
{"title":"Oliver Twist","subTitle":{"present":true}}
これは奇妙に見えるかもしれませんが、実際には私たちが期待すべきことです。
この場合、 isPresent()は、オプションのクラスのパブリックゲッターです。 これは、空かどうかに応じて、trueまたはfalseの値でシリアル化されることを意味します。 これは、Jacksonのデフォルトのシリアル化動作です。
考えてみれば、字幕フィールドの値を実際にシリアル化する必要があります。
2.4. デシリアライズ
ここで、前の例を逆にしましょう。今回は、オブジェクトを逆シリアル化して、
@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の値を引数として取ることができるコンストラクターが必要です。 これは、オプションのフィールドには当てはまりません。
3. 解決
ジャクソンが空のオプションのをnull、として扱い、現在のオプションのをその値を表すフィールドとして扱うことが必要です。
幸い、この問題は解決されました。 Jacksonには、オプションを含むJDK8データ型を処理するモジュールのセットがあります。
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. デシリアライズ
それでは、逆シリアル化のテストを繰り返しましょう。 本を読み直すと、 JsonMappingException:が発生しなくなっていることがわかります。
Book newBook = mapper.readValue(result, Book.class);
assertThat(newBook.getSubTitle()).isEqualTo(Optional.of("The Parish Boy's Progress"));
最後に、今度はテストをもう一度繰り返しましょう。
assertThat(newBook.getSubTitle()).isEqualTo(Optional.empty());
4. 結論
JDK 8 DataTypesモジュールを活用してこの問題を回避する方法を示し、Jacksonが空のオプションのを null、、および現在のオプションとして処理できるようにする方法を示しました。 通常のフィールドとして。
これらの例の実装は、GitHubのにあります。 これはMavenベースのプロジェクトであるため、そのまま実行するのは簡単です。