7月23日、OpenJDKが「JEP 540: Simple JSON API (Incubator)」を公開した。外部ライブラリなしでJSONを扱える標準APIをJavaプラットフォームに追加するインキュベーター提案で、約10年越しの再挑戦となる。
JavaでJSONを扱うとなると、JacksonやGsonの依存追加が半ば当然のこととして行われてきた。ちょっとした値を取り出したいだけでも、pom.xml を書き換え、ライブラリをセットアップするのが常識だった。JEP 540はその状況を変える提案だ。JDK標準のAPIとしてJSONのパース・生成機能を提供し、外部依存なしでJSONを扱えるようにする。
約10年越しの再挑戦
同様の提案は2014年にもJEP 198(Light-Weight JSON API)として出ていたが、「適切な設計を見つけるのが難しい」として棚上げになっていた経緯がある。当時はJSONエコシステム自体が急成長しており、「どの用途に絞るか」の合意形成が難しかったと見られる。それから10年、Javaはsealed interfaceやパターンマッチといった言語機能を獲得し、設計上の選択肢が広がったことも今回の再挑戦を後押しした。
なお、インキュベーター(Incubator)とは、JDKに「実験的に」APIを収録し、実際のユーザーフィードバックを得てから正式標準化を判断するJavaの仕組みだ(JEP 11で定義)。パッケージ名は jdk.incubator.json となり、将来は変更・削除の可能性もあるが、正式標準化への布石として位置づけられている。
コードで見るシンプルさ
一番わかりやすい例として、JEPでは米国気象局のREST APIから取得した気温データの平均を計算するコードを示している。JSONレスポンスは properties.periods[] の中に各時間帯の気温が入ったネスト構造で、新APIを使うと次のように書ける:
String body = ... REST response body, which is a JSON document ... ;
JsonValue json = Json.parse(body);
json.get("properties").get("periods").asList().stream()
.mapToInt(j -> j.get("temperature").asInt())
.average()
.ifPresent(System.out::println);
Json.parse() で JsonValue のツリーを取得し、.get() でキーを辿り、.asInt() で値を取り出す。Stream APIとの親和性も高く、Javaらしい書き方のまま完結する。
JSON生成も同様にシンプルだ:
System.out.println(JsonObject.of(Map.of("providers",
JsonArray.of(List.of(JsonString.of("SUN"),
JsonString.of("SunRsaSign"),
JsonString.of("SunEC"))))));
出力:
{"providers":["SUN","SunRsaSign","SunEC"]}
APIの構造
APIの中核は JsonValue インターフェースで、JSONの値型を表す。JSONの型に対応する6つのサブインターフェースが定義されている:
JsonString— 文字列JsonNumber— 数値JsonBoolean— 真偽値JsonNull— nullJsonObject— オブジェクト({})JsonArray— 配列([])
JsonValue は sealed インターフェース(Java 17で正式導入されたJEP 409)として設計されており、switch 式で網羅的なパターンマッチが書ける。default 節なしでコンパイラの型チェックを活用できる点が特徴だ。
ナビゲーションは .get(String) でオブジェクトのメンバーに、.get(int) で配列の要素にアクセスする。型が合わない場合や対象が存在しない場合は JsonValueException がスローされ、エラーメッセージには行番号と位置が含まれるため、デバッグもしやすい:
jdk.incubator.json.JsonParseException: The duplicate member name: "foo" was
already parsed. Location: line 42, position 69
意図的に絞り込まれた設計
このAPIが目指す範囲は意識的に狭く設定されている。JEPが非目標として明示しているのが「JacksonやGsonといった既存ライブラリを置き換えること」だ。
具体的に省かれている機能:
- 複数のパース設定オプション
- JSON5等の拡張構文
- データバインディング(JavaオブジェクトとJSONの相互変換)
- イベントベースのストリーミング処理
パースはRFC 8259への厳格な準拠のみをサポートし、末尾カンマやコメントは非対応。オブジェクト内の重複キーもエラーとする。複雑なユースケースには引き続きJackson等を使い、標準APIは「シンプルな操作」に集中するという位置づけだ。
JDK自身がJSONを必要としている
JEP 540のもう一つの動機として、JDK自身がJSON設定ファイルを扱えるようになる点が挙げられている。現在JDKはプロパティファイル形式(.properties)を使っているが、このフォーマットは構造化データを表現できない。例えばセキュリティプロバイダーの列挙は:
security.provider.1=SUN
security.provider.2=SunRsaSign
security.provider.3=SunEC
これがJSONなら:
{
"providers": [ "SUN", "SunRsaSign", "SunEC" ],
...
}
JDKは外部ライブラリへの依存を持てない制約があるため、標準APIとして用意する必要があった。また、jcmd ツールが出力するスレッドダンプのJSON形式への対応なども視野に入っている。
「シンプルな作業をシンプルに」というJavaの近年の方針
JEP 540は、Javaが近年進めてきた「小さなプログラムや単純な作業をシンプルに書けるようにする」という方針の延長線上にある。JEPでは関連する動きとして以下が列挙されている:
特にJEP 512との組み合わせは、ちょっとしたJSONを扱うスクリプトをJavaで書く際の恩恵が大きい。「Pythonならワンライナーで済む」という場面を、Javaでも少ないコードで対処できるようになる。
現時点ではインキュベーター段階であり、正式APIへの昇格はフィードバック次第だ。まずは実際に試して、OpenJDKのメーリングリストやフィードバックチャネルに意見を送るのが、この提案を前進させる最短の道だろう。
詳細はJEP 540: Simple JSON API (Incubator)を参照していただきたい。