2018-10-08

JMESPath

JSON用のクエリーがほしいなぁと思いいろいろ探していたのだが、まともな仕様があるものが少ない。JSONPathは超有名なブログ記事のみで今一不安だし、JSONiqはちょっとしたクエリ書くだけにしては巨大すぎる感があるし、JSON Queryでググると山のようにオレオレ実装が出てきて嫌気がしたというのがある。(今思えばJSONiqでもよかった気はする)

そんな中で手ごろなサイズでそれなりに仕様が固まっていて、AWS CLIでも使われているらしいJMESPathなるものを知った。名前は微妙だけど(提唱者名が入ってる)AWS CLIで使われているということはそれなりに実績もあるんだろうし、準拠テストもあるしこれでいいかということで実装してみた。(ここまで前置き)

ということで、こんな感じで使える
(import (rnrs) (text json jmespath))

((jmespath "a") '#(("a" . "foo") ("b" . "bar") ("c" . "baz")))
;; -> "foo"
前に作った(text json pointer)と似たような使用感にしてある(再利用可能という意味で)。後、自分が必要だという理由でいくつか追加の手続きを追加している。名前は以下
  • parent
  • unique
  • remove
  • remove_entry
  • is_odd
  • is_even
名前見れば大体何するか分かりそうなので説明は割愛。詳しくはドキュメント読んで。

実装する上で辛かったこと
JMESPathは一応BNFが定義されているのだが、これが左再帰バリバリで書かれている。しかも、各言語での実装はBNFからパーサを作ってるタイプではないので(Javaの実装はANTLRを使っているのでBNFそのままだったが)、そのままPEGに移植することができない。しょうがないので左再帰を全部除去しつつ、ASTの意味が著しく異ならないようにするのにものすごく苦労した。この辺りの経験値はまだ低いなぁと実感。

また、仕様に書かれている例と準拠テストの挙動が違ったり、仕様がやけに曖昧だったりと結構落とし穴が多かった。(個人的にProjection周りの挙動が仕様からは読み取れなかったので、実装を確認する必要があった)

実装した後で発覚した事実
さて実装し終わったし使い倒すぞ!と意気込んでみたら、実はあまり必要ない感じになっている。結局JSONの構造が分かっていないと使えないものではあるので(GLOBみたいなネスとした曖昧マッチはない)、JSONPointerとベクタライブラリでことが足りるのではという感が出てきている。ベクタJSONの簡易な変形が目的なら使い出があるかもしれない。