このブログは、旧・はてなダイアリー「檜山正幸のキマイラ飼育記 メモ編」(http://d.hatena.ne.jp/m-hiyama-memo/)のデータを移行・保存したものであり、今後(2019年1月以降)更新の予定はありません。

今後の更新は、新しいブログ http://m-hiyama-memo.hatenablog.com/ で行います。

型解析:タグ付きJSONパス

型解析でもJSONパスは大活躍する。JSONパスでは、配列インデックス(非負整数値)とオブジェクトプロパティ名(文字列)により部分構造にアクセスする。インデックス/プロパティ名によるアクセスには、ブラケット記号('[', ']')とドット記号('.')が使われるが、ここではドットだけを使う。

ドットオンリーのJSONパス

プロパティ名は、原則として引用符付き文字列リテラルとして表現する。次は、JSONパスの例となる。

  • $."foo"."bar baz".2
  • $.0."foo".3."2"

「.2」と「."2"」が違うことに注意。空白や特殊文字を含まず、数値と誤解されない文字列に関しては引用符を省略できるとする。

  • $.foo."bar baz".2
  • $.0.foo.3."2"

以下では、get(data, path) という関数(メソッドではない)で、データの部分構造を抜き出せるとする。また、exists(data, path) は、パスが指す場所に値が存在するかどうかを調べる述語関数だとする。

JSONパスをgetやexistsに渡すときは、"$.foo.\"bar baz\".2", "$.0.foo.3.\"2\"" のように引用符のエスケープが必要である。シングルクォートやトリプルクォートが使えれば、'$.foo."bar baz".2', '''$.0.foo.3."2"''' のように書ける。

タグチェック

@foo のようなタグをJSONパスに挿入できる。次の例を見てみる。


@secretData
@person {
"name" : @personName "檜山",
"contact" : @contactInfo {
"mail" : @mail "hiyama@chimaira.org",
"url" : @url "http://d.hatena.ne.jp/m-hiyama/"
}
}

このデータに対して、通常のJSONパスはタグを無視してアクセスする。

JSONパス 取り出される値
$.name @personName "檜山"
$.contact.url @url "http://d.hatena.ne.jp/m-hiyama/"

JSONパスにタグが挿入されると、データのタグを確認し、マッチしないとエラーにして、さらにタグを取り除いた値を返す。

JSONパス 取り出される値
$@publicData エラー
$@secretData @person { ...}
$@person エラー
$@secretData@person {"name": ...}
$@secretData.name @personName "檜山"
$@secretData.name@personName "檜山"
$@secretData@person {"name": ...}
$.contact @contactInfo {"mail": ...}
$.contact@contactInfo {"mail": ...}
$.contact@contactInfo.mail @mail "hiyama@chimaira.org"
$.contact@contactInfo.mail@mail "hiyama@chimaira.org"
$.contact@contactInfo.mail@url エラー
$.contact.mail@mail "hiyama@chimaira.org"

次のようなワイルドカードパターンがあると便利そうだが、型解析の文脈では関係しない。

  1. @* -- 任意のタグ
  2. @foo? -- @foo またはタグがない
  3. @*? -- 任意のタグ、またはタグがない
  4. @** -- 任意個のタグの連続
  5. @*+ -- 1個以上のタグの連続

条件付きワイルドカード

「#」を数値ワイルドカード、「*」を文字列ワイルドカードに使える。これを精密化して、次のような条件付きワイルドカードを導入する。nは非負整数定数、αiは名前文字列である。

  • #>n -- nより大きいすべての非負整数にマッチ
  • #>=n -- n以上のすべての非負整数にマッチ
  • [^α1, ..., αk] --- α1, ..., αk 以外のすべての名前にマッチ

例:

  • mailAddrs.#>1 -- mailAddrs[2], mailAddrs[3], ... などにマッチ
  • mailAddrs.#>=1 -- mailAddrs[1], mailAddrs[2], ... などにマッチ
  • $@person.[^"name","contact"] -- α≠"name", α≠"contact" である $@person.α にマッチ

注意:ドットの代わりにブラケットを使うと、ブラケットのなかに [^"name","contact"] が入って、ブラケットが二重になる。なんか細工した方がいいかも。

pvalidate関数

Tを型表現として、validate(data, T) が妥当性検証関数とする。validate(get(data, path), T) を pvalidate(data, path, T) とする。ただし、pathにはタグチェックとワイルドカードを含めてよい。

  1. パスのタグチェックに失敗したときは、pvalidateはfalseを返す。
  2. ワイルドカードがあれば、dataに対して有効なパスと、ワイルドカードとのマッチを行い、マッチするすべてのパスに対して検証する。

例えば、pvalidate([0, 1, 2, 3], "$.#>=1", "integer") は次の検証をする。

  1. validate(1, "$.1", "integer")
  2. validate(2, "$.2", "integer")
  3. validate(3, "$.3", "integer")

"$.#>=1" が、"$.1", "$.2", "$.3", "$.4", ... に展開され、実際のデータに対して有効なパス "$.1", "$.2", "$.3" だけがチェックされる。