本格派エンジニアの工具箱

第27回JSONデータを扱うためのJavaライブラリ「Jackson Java JSON-processor」

「Jackson Java JSON-processor」とは

近年のWebアプリケーション開発では、JSONが必須の技術になっています。クライアントサイドで利用されるJavaScriptで容易に扱えることから、Webサーバとのデータ交換のためのフォーマットとして広く利用されているからです。Javaアプリケーションも例外ではなく、次期Java EE仕様であるJava EE 7JSR 343として仕様策定中)にもJSONを扱うためのAPIが標準で取り込まれる見込みになっています。

それに伴い、Java用のJSON APIの標準仕様についても、JSR 353として標準化プロセスに入っています。その他にも、すでにJavaでJSONを扱うための様々なライブラリが存在しています。中でもJackson Java JSON-processorgoogle-jsonはJSR 353にも既存実装として名前が挙がっており、標準仕様へ与える影響も大きいと思われます。

今回は、そのうちの「Jackson Java JSON-processor」⁠以下、Jackcon)の使い方を紹介します。JacksonはLGPL 2.1またはApache License 2.0のもとで開発されているオープンソースのJSONライブラリです。JacksonではJavaプログラムからJSONデータを扱うために次の3種類の方法が提供されています。

  • ストーミングAPIによるJSONデータへのアクセス(XMLのStAXに相当)
  • ツリーモデルでのJSONデータへのアクセス(XMLのDOMに相当)
  • JSONオブジェクトとJavaオブジェクトのバインディング

さらに、データバインディングには、JavaのMapやList、Stringなど基本的なオブジェクトへのバインディングと、汎用的なJava Beanオブジェクトへのバインディングの2種類が用意されています。

ストリーミングAPIでJSONデータを読み込む

まずはストリーミングAPIを使ってJSONデータの読み込みを行ってみましょう。ストリーミングAPIは、XMLにおけるStAX(Streaming API for XML)と同様に、JSONデータに対してストリーミング形式でアクセスするためのAPIです。JacksonのストリーミングAPIでは、JSON形式のファイルやストリームの先頭から、トークン単位でデータを取り出すことができます。そのためにはまず、パーサーの機能を持ったJsonParserオブジェクトを作成しましょう。これはJsonFactoryクラスを使って次のように行います。

// JsonFactoryの生成
JsonFactory factory = new JsonFactory();
// JsonParserの取得
JsonParser parser = factory.createJsonParser(new File("sample.json"));

この例ではcreateJsonParser()メソッドに読み込み元となるJSONファイルのFileインスタンスを渡していますが、その他に入力ストリームやURLのインスタンス、JSON形式の文字列やバイト配列を指定することもできます。

作成したJsonParserに対してnextToken()メソッドを実行すると、現在位置の次のトークンがJsonTokenオブジェクトとして取得できます。トークンには次のような種類があります。これはJsonTokenクラスに列挙型として定義されています。

  • START_ARRAY - 配列の開始('[')
  • END_ARRAY - 配列の終了(']')
  • START_OBJECT - オブジェクトの開始('{')
  • END_OBJECT - オブジェクトの終了('}')
  • FIELD_NAME - フィールド名
  • VALUE_EMBEDDED_OBJECT - 組み込みオブジェクト形式の値
  • VALUE_FALSE - "false"値
  • VALUE_TRUE - "true"値
  • VALUE_NULL - nulla値
  • VALUE_NUMBER_FLOAT - float型の値
  • VALUE_NUMBER_INT - int型の値
  • VALUE_STRING - 文字列形式の値

たとえば、次のようなJSONデータがあったとすると、JsonParserによってのようなトークンに分解され、netxToken()メソッドで1つずつ取り出すことができます。

[ { "name" : { "first" : "太郎", "last" : "技評" } } ]
図1 JsonParserによるトークンへの分解
図1 JsonParserによるトークンへの分解

現在位置のトークンのデータを取り出すためには、JsonParserのgetXXXX()形式のメソッドを使います。例えばgetText()メソッドでは現在のトークンのテキストを取得することができます。つまり、基本的にはgetNext()で取得したトークンが目的の種類のものであったら、getXXXX()メソッドでその中身を取り出すという流れで処理を進めていきます。

以上を踏まえて、次のようなJSONデータについて考えてみましょう。

[{
  "name" : { "first" : "太郎", "last" : "技評" }, 
  "mail" : "taro@example.jp", 
  "todo" : { "work" : "hogehoge", "limit" : "2012/02/13" }
}, 
{
  "name" : { "first" : "次郎", "last" : "技術" }, 
  "mail" : "jiro@example.jp", 
  "todo" : { "work" : "hogehoge", "limit" : "2012/02/15" }
}, 
{
  "name" : { "first" : "花子", "last" : "評論" }, 
  "mail" : "hanako@example.jp", 
  "todo" : { "work" : "hogepiyo", "limit" : "2012/02/28" }
}]

次のコードは、このJSONの内容を一覧表示する例です。

// JsonFactoryの生成
JsonFactory factory = new JsonFactory();
// JsonParserの取得
JsonParser parser = factory.createJsonParser(new File("sample.json"));
      
// 配列の処理
if (parser.nextToken() == JsonToken.START_ARRAY) {
    while (parser.nextToken() != JsonToken.END_ARRAY) {
	// 各オブジェクトの処理
	if (parser.getCurrentToken() == JsonToken.START_OBJECT) {
            while (parser.nextToken() != JsonToken.END_OBJECT) {
		String name = parser.getCurrentName();
		parser.nextToken();
		// "name"フィールド
		if ("name".equals(name)) {
		    System.out.println(name + ": ");
		    while (parser.nextToken() != JsonToken.END_OBJECT) {
			if (parser.getCurrentToken() == JsonToken.FIELD_NAME) {
			    System.out.print("    " + parser.getText() + ": ");
			} else if (parser.getCurrentToken() == JsonToken.VALUE_STRING) {
			    System.out.println(parser.getText());
			}
		    }
		}
		// "mail"フィールド
		else if ("mail".equals(name)) {
		    System.out.println(name + ": " + parser.getText());
		} 
		else {
		    parser.skipChildren();
		}
            }
	}
	else {
            parser.skipChildren();
	}        
    }
}
else {
    parser.skipChildren();
}

配列の開始と終了、オブジェクトの開始と終了を基準に、その中身のフィールド名と値を順番に取得しして表示しています。現在のトークンのフィールド名はgetCurrentName()メソッドで取得できるので、これを元にして"name"か"mail"かを判断しています。

このプログラムの出力結果は次のようになります。

name: 
    first: 太郎
    last: 技評
mail: taro@example.jp
name: 
    first: 次郎
    last: 技術
mail: jiro@example.jp
name: 
    first: 花子
    last: 評論
mail: hanako@example.jp

ストリーミングAPIでJSONデータを作成する

続いて、ストリーミングAPIを使ってJSON形式のデータを作成してみましょう。JSONデータの作成にはJsonGeneratorクラスを使用します。JsonGeneratorのインスタンスはJsonFactoryクラスのcreateJsonGenerator()メソッドを使って次のように行います。

// JsonFactoryの生成
JsonFactory jsonFactory = new JsonFactory();
// JsonGeneratorの取得
JsonGenerator generator = 
    jsonFactory.createJsonGenerator(new File("output.json"), JsonEncoding.UTF8);

上記のようにした場合、最終的にourput.jsonという名前のファイルにUTF-8形式でデータが出力されることになります。Fileオブジェクトの他に出力ストリームを対象にすることもできます。

このJsonGeneratorオブジェクトに対して、writeXXXX()のメソッドを使ってデータを追加していきます。例えば「{"first":"太郎","last":"技評"}」というようなデータを作成したい場合には次のようなコードになります。

generator.writeStartObject();
generator.writeFieldName("first");
generator.writeString("太郎");
generator.writeFieldName("last");
generator.writeString("技評");
generator.writeEndObject();

writeStartObject()とwriteEndObject()はオブジェクトの開始と終了を、writeFieldName()はフィールド名を、writeString()は文字列形式の値を追加するメソッドです。文字列形式のデータは、writeTextField()メソッドを使ってフィールド名と値をセットにして追加することもできます。

最後に、flush()メソッドを実行することで、作成されたデータが対象のファイルやストリームに出力されます。次の例は、配列やオブジェクトのネストを含んだデータを出力する例です。

// JsonFactoryの生成
JsonFactory jsonFactory = new JsonFactory();
// JsonGeneratorの取得
JsonGenerator generator = 
    jsonFactory.createJsonGenerator(new File("output.json"), JsonEncoding.UTF8);

// 配列の開始
generator.writeStartArray();
      
// オブジェクトの書き込み
generator.writeStartObject();
generator.writeFieldName("name");
generator.writeStartObject();
generator.writeStringField("first", "太郎");
generator.writeStringField("last", "技評");
generator.writeEndObject();
generator.writeStringField("email", "tato@example.jp");
generator.writeEndObject();

// オブジェクトの書き込み
generator.writeStartObject();
generator.writeFieldName("name");
generator.writeStartObject();
generator.writeStringField("first", "次郎");
generator.writeStringField("last", "技術");
generator.writeEndObject();
generator.writeStringField("email", "jiro@example.jp");
generator.writeEndObject();

// 配列の終わり
generator.writeEndArray();

// ファイルへの書き出し
generator.flush();

このコードによる出力例は次のようになります(見やすいように改行を入れてあります⁠⁠。

[{
  "name":{"first":"太郎","last":"技評"},
  "email":"tato@example.jp"
},{
  "name":{"first":"次郎","last":"技術"},
  "email":"jiro@example.jp"
}]

このように、ストリーミングAPIでは先頭から順番にデータを扱うため、処理がシンプルで極めて高速に動作します。一方で、特定のデータをピンポイントで取り出したい場合などには向いていませんです。そこで、もう少し柔軟なデータへのアクセスができる方法としてツリーモデルのAPIがあります。次回はその使い方を解説します。

おすすめ記事

記事・ニュース一覧