JSONデータの一部にアクセスするためのパス式としては、既に JSONPath (http://goessner.net/articles/JsonPath/) や JPath (http://bluelinecity.com/software/jpath) があります。どちらも、XPathのJSON版という位置づけ。これらは便利そうですが、僕の用途にはオーバースペックなので、もっと簡単で小さいな構文を考えてみました。
まず、基本的な構文要素。一部、コメントでお茶を濁してますが、カンベン。
UsualChar ::= {文字列内でエスケープを必要としない印字可能文字} Escaped ::= {特殊な文字のエスケープ表現} QuotedString ::= '"' (UsualChar|Escaped)* '"' NonNegativeInteger ::= '0' | [1-9] [0-9]* NameStartChar ::= {名前の最初に使える文字、数字は含まれない} NameChar ::= {名前に使える文字} Name ::= NameStartChar NameChar*
パス式の構文は次のとおり。
Path ::= '$' | Name | Path '.' Name | Path '.' NonNegativeInteger | Path '[' NonNegativeInteger ']' | Path '[' QuotedString ']'
説明と注意:
- '$' はデータの「ルート」を表す。JSONPath仕様から拝借。
- foo.2 という書き方も許す。これは、foo[2] と同じ。
- foo["bar"] という書き方も許す。これは、foo.bar と同じ。
- プロパティ名が名前でないときは、foo["hello, world."] のような書き方しかできない。
- foo . bar とか foo [ 2 ] と書いても問題は起きないのだが、余分な空白は禁止としておく*1。
- パス式を文字列データとして書くと、"foo[\"bar\"]" のようになる。まー、しょうがないですね。
次の曖昧性に気がつきます。
- foo["1"] と foo[1] は同じか?
JSONスキーマでは、object型とarray型は排他的(両方に所属するインスタンスはない)と捉えているようです。foo["1"] はオブジェクト(object型のデータ)に対して適用され、foo[1]は配列(array型のデータ)に対して適用されると考えると、次の解釈が妥当でしょう。
- foo["1"] と foo[1] は同じではない。
実例を挙げましょう。([追記]JavaScriptでは、foo["1"] == foo[1] なので、そのままJavaScriptの式として解釈はできなくなります。[/追記])
{ "foo" : { "0": "apple", "1": "orange"}, "bar" : ["apple", "banana"] }
上の例に関して:
- foo[1] はエラー(構文エラーではなく実行時エラー)
- foo.1 はエラー(foo.1 と foo[1] は等価なので)
- foo["1"] の値は "orange"
- bar[1] の値は "banana"
- bar.1 の値は "banana"
- bar["1"] はエラー
こんなもんでもけっこう足りるんじゃないのかな。
*1:説得的な必要性があれば、空白を解禁します。