PullParserによるJSONのパース

JavaFX Scriptは非常に使いやすい言語仕様なのですが、数少ない不満点はマップ(ハッシュ・辞書)に相当する構文がないことです。JavaAPIはシームレスに使えるのでjava.util.Mapは使用できますが、いまいち使いにくい事は否めません。これが顕著に現れてしまうのはJSONのParseではないでしょうか。
今回はちょっとわかりにくいJSONのパースについてAPIの使い方を解説します。基本的な考え方はXMLと同じようにみなしてイベントが発生するということです。

javafx.data.pull.PullParser

PullParserは、JavaFXXMLJSONをパースするためのクラスです。APIStAXを使ったことがあれば、ほぼ同じ仕組みになっています。つまり、入力に対してパースを行うと、頭から解析してゆき、区切りとなる場面でイベントがコールされます。イベントは、ドキュメントの開始だったり、配列の開始・終了だったりと「どんなイベント」が発生したかはオブジェクトに詰め込まれているため、指定された関数で特定のイベントをまって処理をするような形になります。

サンプルコードです。

var text = '\{'
'    "x": 10,'
'    "y": 20'
'\}'
;
def parser:PullParser = PullParser {
    documentType: PullParser.JSON
    input: new ByteArrayInputStream(text.getBytes())
    onEvent: function(event) { println(event); }
};
parser.parse();

このようにPullParser にはJSONXMLかを指定するdocumentType、入力のストリームを指定するinputがあり、onEventで各種のイベントを処理します。この状態でparseを実行すれば頭から読み込み、イベントが発生していく形です。

尚、StAXも同じ形式ですが、これはSAXの後継として作成されたようです。SAXとは似ていますが、SAXの場合はイベントオブジェクトに状態を持つのではなく、Parserクラスの状態がパースしながら変化する点、処理をフックしなければならないポイントが多数ある点などが異なります。

JSONの配列をパースする

JSONの配列をパースする場合に発生するイベントは次のようになります。

XMLで考えると次のような記述に相当することになります。

<array>
  <arrayElement>10</arrayElement>
  <arrayElement>20</arrayElement>
</array>

JSONのハッシュをパースする。

JSONの配列をハッシュする場合に発生するイベントは次のようになります。

XMLの場合に対応しやすいように、ハッシュはELEMENTとして扱われます。

XMLで考えると次のような記述に相当することになります。

<element>
  <value name="x">10</value>
  <value name="y">20</value>
</element>

START_VALUEの中にキーが含まれていることに注意しましょう。

線形的な処理

parse関数を使った処理はXMLJSON)を最初から最後まで一気に読み込んで生きます。しかし、特定のポイントのデータのみが欲しいような場合は、イベントを逐一ハンドリングせずに、指定したイベントを直接的に取り出すことができます。
forward/seekの2つのメソッドがこれに対応しますが、forwardはイベントを指定した分だけ先に進ませる関数、seekは条件を指定してそこまでイベントを進める関数です。これらの関数を使用すると、最後まで読み込みは行いません。実行後にeventプロパティから欲しい値を取得する形になります。

var text = '\{'
'    "x": 10,'
'    "y": 20'
'\}'
;
def parser:PullParser = PullParser {
    documentType: PullParser.JSON
    input: new ByteArrayInputStream(text.getBytes())
};
parser.seek("y").forward(); // name=yのイベントまで進み、もう1つ進む(value)
println(parser.event.integer); // => 20

ただし、イベントをもう1度読み直すなどはできませんので注意しましょう*1

*1:InputStreamから順次読み込みです