Java APIによるJSON処理 - Oracle › ... › javamagazine ›...

6
ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016 19 //enterprise java / J avaScript 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オブジェクト・モデルAPI JSON-Pオブジェクト・モデルAPIは、JSONデータ構造を表すインメモリのツ リー状構造に基づいたもので、簡単に問合せを行えるようになっています。 また、このAPIによってJSONツリー構造内を移動することもできます。この APIは使いやすいものですが、メモリを多く消費します。後ほど紹介する低 レベルのストリーミングAPIほど効率的ではないことに注意してください。 オブジェクト・モデルAPIは、 JsonObjectJsonArrayJsonStrin gJsonNumberというクラスでさまざまなJSONデータ・タイ プをサポートしています。さらに、 JsonValueクラスには、特定 のJSONの値(true、 false、およびnull)を表す定数が定義されて います。 JsonValueは、 JsonObjectJsonArrayJsonStrin gJsonNumberの共通のスーパー・タイプでもあります。 オブジェクト・モデルAPIはjavax.jsonパッケージにあり、2つの主要 なインタフェースJ sonObjectおよび 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

Transcript of Java APIによるJSON処理 - Oracle › ... › javamagazine ›...

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