List.ofがJacksonでdeserializeできない話

こんにちは。 エキサイト株式会社の三浦です。

JavaにはJacksonというライブラリがあり、Javaコード上のデータをJSONに変換したり(serialize)、逆にJSONJavaコード上のデータに変換したり(deserialize)してくれます。 今回は、このJacksonを使った時にある条件下で詰まった話をしていきます。

Jackson

Jacksonは、Githubのページで以下のように定義されています。

Jackson has been known as "the Java JSON library" or "the best JSON parser for Java". Or simply as "JSON for Java".

端的に言えばJava用のJSONライブラリで、JSONJavaコード上データの相互変換等をしてくれます。 例えば、APIでリクエスト元にデータを返す時にJavaコード上のデータをJSONに変換してから返したり、キャッシュ保存時にデータをJSON化してから保存し、逆にキャッシュからデータを取得する時にJSONJavaコード上のデータに変換する、などで使われたりします。

大体のデータであればJacksonを使えば相互変換ができるのですが、実はまだ対応していない条件があります。 それが List.of です。

List.of と Jackson

List.of はJava9から追加されたメソッドで、immutableなリストを提供してくれます。 すなわち、 List.of で定義したリストは追加や削除、更新することができず、安全に取り扱えるため、非常に使い勝手がよいわけです。

List<String> sampleList = List.of("a", "b");

// 追加できない
sampleList.add("c");

ただ、残念ながらこの List.of はJacksonには対応していません。

// SAMPLE_KEYを使ってキャッシュする
@Cacheable(cacheNames = CacheKeyType.SAMPLE_KEY)
public List<String> getSampleList() {
    return List.of("a", "b");
}

このようにキャッシュをすると、キャッシュからデータを取得する時に Could not read JSON が発生します。 キャッシュデータを見ると以下のように保存されています。

["a", "b"]

一見問題ないように見えますが、実はJacksonではJSON化する際に変換元データの型も保存するようになっており、それをJSONから戻す際に使用するという仕様になっています。 List.of では型を保存してくれないので、Jacksonでは戻すことができないのです。

また、 List.of の使い方によっては型を保存してくれる場合もあるのですが、どうやら List.of で作られた型はまだJacksonで対応されていない型のようで、いずれにしろエラーが起きてしまいます。

解決方法

もっとも安易な解決方法は、 List.of を使わないことでしょう。 例えば上記であれば、 List.of の代わりに Arrays.asList を使うことができます。

// SAMPLE_KEYを使ってキャッシュする
@Cacheable(cacheNames = CacheKeyType.SAMPLE_KEY)
public List<String> getSampleList() {
    return Arrays.asList("a", "b");
}

こちらだとキャッシュでは、以下のように保存されます。

[
    "java.util.Arrays$ArrayList",
    ["a", "b"]
]

こちらであればJacksonに対応している型が保存されるため、Jacksonで問題なく戻すことができます。

可能であれば、

// 空リストを作るとき
Collections.emptyList();

// 1件だけのリストを作るとき
Collections.singletonList("a");

// 2件以上のリストを作るとき
Arrays.asList("a", "b");

とできると、空・1件のみのリストはimmutableになるのでおすすめです。

最後に

List.of は非常に便利ですが、このように落とし穴があるため気をつける必要があります。 状況に合わせて使い分けていきましょう。

なお、2021年9月6日現在では List.of がJacksonで使用できませんが、今後のアップデートで使用できるようになる可能性があります。 また、見つけられていないだけで、実は現状でも使えるようになる設定がある可能性もあります。

あらかじめご了承下さい。