Download - Java APIによるJSON処理 - Oracle › ... › javamagazine › Java-JA16-JSONP.pdfAPIが定義されています。なお、バインディング(JavaオブジェクトのJSON

Transcript
Page 1: Java APIによるJSON処理 - Oracle › ... › javamagazine › Java-JA16-JSONP.pdfAPIが定義されています。なお、バインディング(JavaオブジェクトのJSON

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

19

//enterprise java /

JavaScript Object Notation(JSON)は、軽量データ交換を実現する技術です。JSONはXMLの代わりに使われることが多く、それぞれに明らかな利点と欠点があります。XMLは強力ですが、その強力さには複雑という代償が伴います。一方、JSONの機能はXMLに比べて限定的ですが、この特徴は主要なメリットの1つであるシンプルさにつながります。現在、JSONは議論の余地なくインターネットでもっとも一般的に使われているデータ交換形式です。その鍵となっているのは、おそらくこのシンプルさでしょう。JSONは多くの場合にRESTサービスと関連付けられていますが、従来型のエンタープライズ・アプリケーションでもJSONがますます利用されるようになっています。最新バージョンのJava EEであるJava EE 7にはJSONが導入され、Javaプラットフォームへのうれしい追加機能となりました。JSONのサポートは、JSR 353で標準化された新しいJava API for JSON Processing(JSON-P)によって提供されています。この仕様では、JSONドキュメントの解析、生成、変換、問合せといった処理のためのシンプルなAPIが定義されています。なお、バインディング(JavaオブジェクトのJSONドキュメントへのマーシャリングおよびその逆)については、関連APIであるJava API for JSON Binding(JSON-B)で扱われています。JSON-Bは現在、JSR 367として定義作業が行われています。JSON-Pは1つではなく、2つのAPIを提供しています。1つはXML Document Object Model(DOM)APIと同等の高レベル・オブジェクト・モデルAPI、もう1つはStreaming API for XML(StAX)と同等の低レベル・ストリーミングAPIです。本記事では、両方のAPIについて簡単に説明します。

JSON-Pオブジェクト・モデルAPIJSON-Pオブジェクト・モデルAPIは、JSONデータ構造を表すインメモリのツリー状構造に基づいたもので、簡単に問合せを行えるようになっています。

また、このAPIによってJSONツリー構造内を移動することもできます。このAPIは使いやすいものですが、メモリを多く消費します。後ほど紹介する低レベルのストリーミングAPIほど効率的ではないことに注意してください。オブジェクト・モデルAPIは、JsonObject、JsonArray、JsonString、JsonNumberというクラスでさまざまなJSONデータ・タイプをサポートしています。さらに、JsonValueクラスには、特定のJSONの値(true、false、およびnull)を表す定数が定義されています。JsonValueは、JsonObject、JsonArray、JsonString、JsonNumberの共通のスーパー・タイプでもあります。オブジェクト・モデルAPIはjavax.jsonパッケージにあり、2つの主要なインタフェースJsonObjectおよびJsonArrayによって動作しています。JsonObjectは、モデルを表す、順序付けされていないキーと値のペア(0個以上)の集合にアクセスするためのMapビューを提供します。JsonArrayは、順序付けされたモデルの値(0個以上)の列にアクセスするためのListビューを提供します。JsonReader.readObjectメソッドを使うと、入力ソースからいずれかのタイプのインスタンスを作成できます。また、 JsonObjectインスタンスとJsonArrayインスタンスは、次に説明する、活用が容易なAPIを使って構築することもできます。JSONオブジェクトまたはJSON配列を表すモデルを作成するために、オブジェクト・モデルAPIはシンプルなビルダー・パターンを使っています。ビルダー・オブジェクトは、Jsonクラスの静的メソッド(Json.createObject-BuilderまたはJson.createArrayBuilderメソッド)を呼ぶだけで取得できます。次に、ビルダー・オブジェクトに対して複数のaddメソッドの呼出しを連鎖させ、必要なキーと値のペアを追加します。最後にbuildメソッドを呼び出すと、実際に生成されたJSONオブジェクトまたはJSON配列が返されます。

DAVID DELABASSÉE

Java APIによるJSON処理JSONデータの取り扱いを格段にシンプルにする2つの便利なAPI

Page 2: Java APIによるJSON処理 - Oracle › ... › javamagazine › Java-JA16-JSONP.pdfAPIが定義されています。なお、バインディング(JavaオブジェクトのJSON

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

20

//enterprise java /

JSON-P 1.0はJava EE 7(Java EE 7対応のアプリケーション・サーバーであれば、何でも構いません)またはJava SEから利用できます。Mavenを使用する場合は、プロジェクト・オブジェクト・モデル(POM)ファイルに次の2つの依存性を必ず追加してください。

<dependency> <groupId>javax.json</groupId> <artifactId>javax.json-api</artifactId> <version>1.0</version></dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.json</artifactId> <version>1.0.4</version></dependency>

最初のjavax.json-api依存性は、コードをコンパイルするために必要です。2番目の依存性は、JSON-Pのリファレンス実装を参照しています。この依存性は、コンパイルしたJSON-Pのコードの実行に必要です。次の例は、JSON-Pオブジェクト・モデルのビルダーAPIを使用して、国をJSONで表現したものを作成する方法を示しています。この例は、とても一般的なユースケースであるJSONオブジェクトのネストを処理する方法も示しています。// 1) JSONオブジェクト・ビルダーの取得 JsonObject country = Json.createObjectBuilder()

// 2) add the different key/values pairs .add("country", "Belgium") . . . .add("population", 11200000) // JSONオブジェクトはネストできる点に注意 .add("officialLanguages", Json.createArrayBuilder() .add(Json.createObjectBuilder()

.add("language", "Flemish")) .add(Json.createObjectBuilder() .add("language", "French")) .add(Json.createObjectBuilder() .add("language", "German")))

// 3) 生成されたJSONオブジェクトを返却 .build();

【編集注:簡潔かつわかりやすくするために、コード・スニペットでは適切なエラー処理、インポート、適切なリソース管理など、重要ではあるものの関連性がない部分は省略しています。】

JSON-Pオブジェクト・モデルAPIでは、JSONオブジェクト(やJSON配列)への問合せを実行するためのさまざまなgetterメソッドが提供されています。また、APIは不変であり、スレッドセーフである点にも注意してください。このAPIでgetterメソッドのみが提供されており、setterメソッドが提供されていないのはそのためです。getterに渡す最初のパラメータは、検索するキーと値のペアのキーです。2つ目のパラメータは省略可能で、キーが見つからなかった場合のデフォルト値を指定します。

// 国の名前の値を検索String capital = country.getString("country ", "Unknown!"); 次の例では、JsonReaderを使ってファイルからJSONオブジェクトを作成し、複数のJSON-P getterを使って問合せを行い、Java 8のStream APIを使って結果を結合しています。

JsonReader jsonReader = Json.createReader(new FileReader("data.json"));JsonObject country = jsonReader.readObject(); System.out.println("Country: " + country.getString("country", "empty!"));

このAPIではgetterメソッドのみが提供されており、setterメソッドは提供されていません。

Page 3: Java APIによるJSON処理 - Oracle › ... › javamagazine › Java-JA16-JSONP.pdfAPIが定義されています。なお、バインディング(JavaオブジェクトのJSON

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

21

//enterprise java /

int population = country.getInt("population", 0);if (population > 0 ) System.out.println("Population: " + population);

JsonArray langs = country.getJsonArray("officialLanguages");

String offLangs = langs.getValuesAs(JsonObject.class) .stream() .map(lang -> lang.getString("language","")) .collect(Collectors.joining(", ")); System.out.println( "Official languages: " + offLangs);

サンプルJSONデータを使うと、このコードからは次の内容が出力されます。

Country: BelgiumPopulation: 11200000Official languages: Flemish, French, German

オブジェクト・モデルAPIを使うと、インメモリのオブジェクト・ツリー内を移動することもできます。この移動には、共通のスーパー・タイプJsonValueを利用します。次に説明する例では、シンプルな再帰メソッドであるnavigateを使ってツリー構造を移動し、各要素を表示しています。このメソッドは、ツリーの各要素に対してgetValueTypeメソッドを呼び出して実際の要素タイプを取得し、適切な動作を行っています(たとえば、JSON文字列には適切なgetterであるgetStringメソッドを呼び出します)。

public static void navigate ( JsonValue tree, String key) { if (key != null) System.out.print(key + ": ");

switch(tree.getValueType()) { case OBJECT: JsonObject object = (JsonObject) tree; for (String name : object.keySet()) navigate (object.get(name), name); break; case ARRAY: System.out.println(" (JSON array)"); JsonArray array = (JsonArray) tree; for (JsonValue val : array) navigate (val, null); break; case STRING: JsonString str = (JsonString) tree; System.out.println(str.getString()); break; default: // 説明を簡潔にするために、NUMBER, // BOOLEAN、NULLは無視 break; }}このサンプルは、メソッドを呼び出して次のようにJSONオブジェクトを渡すだけで動かすことができます。

navigate (country, null);

オブジェクト・モデルAPIでは、JsonWriterクラスを通してJSONオブジェクト(または配列)をストリームに出力することもできます。まず、Json.createWriterメソッドにより、使用する出力ストリームを指定します。次に、JsonWriter.writeObjectメソッドでJSONオブジェクトをストリームに書き込みます。最後に、出力ストリームをクローズする必要があります。クローズは、JsonWriter.closeメソッドを呼び出すか、次に示す例のようにAutoCloseableな「try-with-resources」アプローチを使用するかのいずれかにより行います。

Page 4: Java APIによるJSON処理 - Oracle › ... › javamagazine › Java-JA16-JSONP.pdfAPIが定義されています。なお、バインディング(JavaオブジェクトのJSON

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

22

//enterprise java /

StringWriter strWriter = new StringWriter();try (JsonWriter jsonWriter = Json.createWriter(strWriter)) { jsonWriter.writeObject(country);}

JSON-PストリーミングAPI2つ目のJSON-P APIは、StAXと同様の概念に基づく低レベルのストリーミングAPIです。このストリーミングAPIは、JSONデータに対してforward onlyオプション指定、、かつ読取り専用のストリーミング的なアクセスを提供するもので、大きなJSONペイロードを効率よく読み取る操作に特化しています。また、ストリーミングAPIを使うと、JSONデータをストリーミング的に出力に書き込むこともできます。 このAPIは、javax.json.streamパッケージに格納されており、JsonParserインタフェースがこのストリーミングAPIの中核となっています。このインタフェースは、プル解析を実行するプログラミング・モデルを使用して、JSONデータに対する前方向のみ、かつ読取り専用のアクセスを提供します。このプル・モデルでは、アプリケーションがJsonParserのメソッドを繰り返し呼び出し、パーサーを進めることで制御を行います。それに基づいてパーサーの状態が変化し、その状態変化を反映したパーサー・イベントが生成されます。

プル・パーサーは、START_OBJECT、END_OBJECT、START_ARRAY、END_ARRAY、KEY_NAME、VALUE_STRING、VALUE_NUMBER、VALUE_TRUE、VALUE_FALSE、VALUE_NULLといったわかりやすい名前のイベントを生成できます。アプリケーションのロジックはこのようなさまざまなイベントを活用してパーサーをJSONドキュメント内の必要な位置まで進め、必要なデータを取得します。まず、Json.createParserメソッドを利用してInputStreamまたはReaderのいずれかからプル・パーサーを作成します。次に、アプリケーションはパーサーのhasNextメソッド(パーサーが最後まで到達していないかを確認)とnextメソッドを呼び出してパーサーを進めます。パーサーは一方向(前方)にしか動けないことに注意してください。次の例では、国関連の情報をJSONで公開している無料のオンライン・サービスを利用しています。このコードでは、単にJson.createParserメソッドを利用してinputStreamからストリーミング・パーサーを作成していま

す。次に、アプリケーションはパーサーを進め、各国のデータをたどってゆきます。今回の例では、2つのキー、nameおよびcapitalのみを探す解析ロジックを使っています。アプリケーションは、各countryについて "name"値を検索し、"France"以外の場合はパーサーを進めます。"France"が見つかると、アプリケーションは"capital"キーのみを探します。現在のパーサーの状態がEvent.KEY_NAME(すなわち、パーサーがFranceのcapitalキーの場所にいる)であるため、アプリケーションはパーサーを1段階進め(Event.VALUE_STRING)、パーサーのgetStringメソッドを利用してcapitalの実際の値を取得します。この操作が完了してしまえば、残りのJSONストリームの解析を続ける必要はなくなります。そのため、アプリケーションはループを終了します。

//1. InputStreamからストリーミング・パーサーを作成するURL url = new URL("http://restcountries.eu/rest/v1/all");try (InputStream is = url.openStream(); JsonParser parser = Json.createParser(is)) { boolean foundCapital = false; boolean foundCountry = false; //2. 「France」のcapitalを見つけるまで //パーサーを進める while (parser.hasNext() && !foundCapital) {

Event e = parser.next(); if (e == Event.KEY_NAME) {

switch (parser.getString()) {

JSON-Pは、ご承知のとおり、Javaベース初のJSON関連APIではありませんが、Java Community Processによって標準化され

た最初のAPIです。

Page 5: Java APIによるJSON処理 - Oracle › ... › javamagazine › Java-JA16-JSONP.pdfAPIが定義されています。なお、バインディング(JavaオブジェクトのJSON

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

23

//enterprise java /

// パーサーが、キーが「name」で値が // 「France」であるペア上に // いるかどうかを判定する case "name": parser.next(); String country = parser.getString(); if (country.equals("France")) { foundCountry = true; } break;

case "capital": if (foundCountry) { // パーサーが「France」のキー/値ペアの上に // いるため、もう1段階前に進めて、 parser.next(); // 実際の値を取得する String capital = parser.getString(); // 残りのドキュメントの解析は不要となる foundCapital = true; } break; } } }}

このサンプルの解析ロジックはとてもシンプルです。また、解析ロジックの要件によっては、このストリーミングによるアプローチを使うことによって作業量が多少増える場合があります。しかし、高レベルのオブジェクト・モデルベースのアプローチよりも明らかに効率的です。同様に、ストリーミング的なアプローチでJSONドキュメントを生成することもできます。次の例をご覧ください。

FileWriter writer = new FileWriter("canada.json");

JsonGenerator gen = Json.createGenerator(writer);gen.writeStartObject() .write("country", "Canada") .write("capital", "Ottawa") .write("poulation", 36048521) .writeStartArray("officialLanguages") .writeStartObject() .write("language", "English") .writeEnd() .writeStartObject() .write("language", "French") .writeEnd() .writeEnd() .writeEnd();gen.close();

JsonGeneratorを使うと、バイト・ストリーム(またはWriter)にJSONを書き込むことができます。このジェネレータを取得するためには、 javax.json.Json.createGeneratorの静的メソッドのいずれかを呼び出します。JsonGeneratorインスタンスを取得した後は、writeStartObject、writeArrayObject、writeといった各種のメソッドを呼び出して目的のJSONオブジェクト表現を構築できます。 writeStartObjectメソッドとwriteArrayObjectメソッドを呼び出すときは、対応するクローズ用のメソッドであるwriteEndを呼び出すことが重要です。最後にジェネレータに対してcloseメソッドを呼び出し、リソースを適切にクローズする必要があります。

Page 6: Java APIによるJSON処理 - Oracle › ... › javamagazine › Java-JA16-JSONP.pdfAPIが定義されています。なお、バインディング(JavaオブジェクトのJSON

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

24

//enterprise java /

まとめJSON-Pは、JSONドキュメントの解析、生成、問合せを行うシンプルなオブジェクト・モデルAPIと、大きな JSONペイロードをストリーミング的に解析および生成する効率的な低レベルAPIを提供します。JSON-Pは、ご承知のとおり、Java ベース初のJSON関連APIではありませんが、Java Community Processによって標準化された最初のAPIです。JSON-PがJava EEの一部になったことを考慮すると、このAPI は使用しているJava EE 7 アプリケーション・サーバーにかかわらず利用できるようになるのは確実でしょう。さらに、JSON-Pには Java EE への依存性はありません。そのため、通常のJava SEアプリケーションでも使用できます。</article>

David Delabassée(@delabassee):Javaについての豊富な経験を持ち、さまざまな Javaカンファレンスで定期的に講演を行っている。現在はOracle に勤めており、サーバー・サイドJavaに注力している。

“Introducing JSON”JSON object model JavadocJSON Stream API Javadoc

learn more