正確性、速度、そしてシンプルさを重視したjqクローン「Jaq」
(github.com/01mf02)- jaqはJSONデータ処理ツール
jqのクローンで、多くの場合でjqとの互換性を保ちながら、より予測可能な実装を目指すもの - コマンドラインプログラム
jaqはjqのドロップイン代替として利用でき、Rustライブラリjaq-coreはRustプログラム内でjqプログラムをコンパイル・実行できる jqにはないYAML、CBOR、TOML、XMLのサポートを提供し、jaq-coreはマルチスレッド環境で安全に利用でき、JSONを超えた任意のデータ型をサポートする- 性能評価では、
jaq-3.0は31件のベンチマークのうち20件で最速、jq-1.8.1は5件、gojq-0.12.18は6件で最速だった - セキュリティ面では、パニック防止、メモリ安全性、入力データとjqフィルタのI/O制限を保証しようとしているが、時間・メモリ・スタックなどのリソース枯渇には対応しない
jaqが提供するもの
- jaqはJSONデータ処理ツール
jqのクローンで、発音は/ʒaːk/、Jacquesと同じ jqにはないデータ形式のサポートがある-
YAML
-
CBOR
-
TOML
- XML
- 別途manualがあり、playgroundで試せる
- jaqは2つの形態で提供される
- コマンドラインプログラム
jaq:jqのドロップイン代替として利用可能 - ライブラリ
jaq-core: Rustプログラム内でjqプログラムをコンパイルして実行可能
-
設計目標
-
正確性
- jaqは多くの場合で
jqとの互換性を保ちながら、より正確で予測可能なjq実装を目指す
- jaqは多くの場合で
-
性能
- jaqはもともと
jq 1.6の長い起動時間が不便だったため作られたもので、その環境での起動時間は約50msだった - 多数の小さなファイルを処理する場合、この起動時間が特に目立つ
jq 1.7で起動時間は大きく改善されたが、jaqは複数のベンチマークで今もjqより速い
- jaqはもともと
-
シンプルさ
- jaqはバグの可能性を減らし、コントリビュートしやすくするため、シンプルで小さな実装を志向する
インストールとビルド
- Linux、Mac、Windows向けのバイナリはreleases pageから入手できる
- macOSまたはLinuxではhomebrewでインストール可能
brew install jaqbrew install --HEAD jaq
- ソースからビルドするにはRustツールチェーンが必要
cargo install --locked jaqcargo install --locked --git https://github.com/01mf02/jaq
- リポジトリをクローンした場合は、
cargo build --releaseまたはcargo install --locked --path jaqでビルドまたはインストール可能 - jaqはRustがサポートするすべてのシステムで動作するはずで、そうでない場合はIssueを登録してほしいと案内している
性能評価
- 性能評価は、jaq、jq、gojqを比較する複数のベンチマークで構成される
emptyベンチマークは、null入力でemptyフィルタをn回実行し、起動時間を測定するbf-fibベンチマークは、jqで書かれたBrainfuckインタプリタで、Fibonacci数を生成するBrainfuckスクリプトを実行する- ベンチマークデータはLinuxシステム上のAMD Ryzen 5 5500Uで生成された
- 使用コマンドは
bench.sh target/release/jaq jq-1.8.1 gojq-0.12.17 | tee bench.json - 表には
jaq-3.0、jq-1.8.1、gojq-0.12.18の結果がミリ秒単位で表示される N/Aはエラーまたは10秒超過を意味する
- 使用コマンドは
- 結果の要約
jaq-3.0は20件のベンチマークで最速jq-1.8.1は5件のベンチマークで最速gojq-0.12.18は6件のベンチマークで最速
gojqはtree-flattenで大幅に速いが、これはflattenフィルタを定義ではなくネイティブに実装しているため
セキュリティモデルと制限
- jaqは以下を保証しようとしている
- リソース枯渇のケースを除き、パニックが発生しない
- メモリを破壊しないメモリ安全性
- jqフィルタ実行前のファイル読み込みを除けば、入力データとjqフィルタがI/O操作を開始できない
- この保証が破られる場合はバグとみなされ、報告対象となる
- jaqはいかなる種類のリソース枯渇にも対応しない
- 実行時間が無制限に長くなり得る
- メモリを無制限に使用し得る
- スタック領域を無制限に使用し得る
- 例として、入力データを読み込むときやjqフィルタを実行するときにスタックオーバーフローが発生し得る
jaq -nr 'repeat("[")' | jaqjaq -n 'def f: 1+f; f'
監査とテスト
- jaq coreは2回のNLnet grantの一部として、Radically Open Securityの監査を受けた
- 1回目と2回目のセキュリティ監査では、中程度または低い深刻度の問題が見つかった
- セキュリティ監査のすべての問題は対応済みで、
jaq-core/fuzzにjaq向けのファジング対象が複数追加された - jaqのJSONパーサーhifijsonにも、すでにファジング対象があった
- jaqには500件以上のテストで構成されるテストスイートがある
ユーザー事例
- あるユーザーは、
jaqが直接jqサポートを実装するより大いに役立ち、ValTtraitによる拡張性のおかげで独自型にjqサポートを簡単に追加できたと評価している - 別のユーザーは、jaqを使うRustプログラムが、Pythonの
jqPyPI crateとPythonループがファイル全体に対して1つのクエリを実行している間に、ファイル全体に対してすべてのクエリを3回実行できたと述べている wsjqインタプリタの事例では、jaqが他のjq実装よりかなり速く、正確性を重視している点が印象的だったとの評価がある- その
wsjqベンチマークで、jaqはjqより5〜10倍、gojqより15〜196倍速い
- その
- certificate transparency logデータを
certstream-serverで処理していたユーザーは、jqパイプ処理で問題があり、jaqに切り替えた後はより速い起動時間のおかげで低スペックVMでも追従できたという
資金提供
- jaqプロジェクトは、NLnetが設立したNGI0 EntrustとNGI0 Commonsファンドを通じて支援を受けた
- 財政支援はEuropean CommissionのNext Generation Internetプログラムから提供された
- 追加資金はSwiss State Secretariat for Education, Research and Innovationから提供された
1件のコメント
Hacker News のコメント
jq の開発が5年間止まっていて、最近ようやく再び動き出したことを考えると、既知のバグであれ新しいバグであれ、その間に報告が積み上がっていたのは不思議ではない
これから勢いを取り戻して、長く積み上がっていた未解決リストを少しずつ片付けていくのだと思う
似ている、または影響を受けたプロジェクトで、代替ではないものを README で一緒に紹介してくれるやり方は良いと思う
このプロジェクトの README で https://github.com/yamafaktory/jql を知った。ずっと探していたツールなのでありがたい
JAQ をけなすつもりはないが、JQ 風の構文はあまりに理解しづらいので、自分には jql のほうが合っている
JSON をキー・値形式の行に平坦化して、
grepのような単純なストリーム処理とうまく合うようにしてくれる: https://github.com/tomnomnom/gronただし、本当に SQL のような体験を期待していた。なぜ単に SQL をまねて、
"SELECT * FROM $json WHERE x>1"のようにクエリできるようにしないのか分からない皆、コードゴルフでもするかのように、自分だけの難解な記号式クエリ言語を作りたがっているように見える。極端に短いが自明ではない昔の Unix 風構文から離れて、PowerShell 寄りのやり方に近づいてほしい
|={"b""d"=2, "c"}は jq のselect(."b"."d" == 2 or ."c" != null)のような意味に見えるが、jq のほうが長くてもより明確に感じる実際には
.[] | select(...)が必要だろうが、jql にも似た前提があるかもしれず、例が完全なのか確信がないので、結論には大きく影響しない自分自身に適用したり、「マクロ」を使ったりすることもできそうだ
jq のアイデアは好きだが、頻繁に使うわけではないので、やりたいことをするには毎回構文をマニュアルで調べる必要がある
残念ながら、jq でやることの 99% は
| jq .だ別途、設定言語を作り始めたのだが、実は JSON クエリにもかなり良いことが分かった: https://docs.ruuda.nl/rcl/rcl_query/
jq では解けなかったが RCL では解けた例がここにある: https://fosstodon.org/@ruuda/111120049523534027
必要な作業と、縮小した元 JSON サンプルを一緒に渡すと、正しい jq スクリプトを作ってくれる
複雑な要件は一度で正確に説明するより、Copilot と少しずつ反復しながら解法へ誘導するほうが簡単で安定している。反復しているうちに、最初より良いアイデアが浮かぶこともある
ChatGPT や他のツールも似たように動作しそうだ
https://github.com/01mf02/jaq/blob/main/Cargo.lock
依存関係がかなり多い
依存関係が多いと、時間が経つにつれて互いに根本的に互換性がなくなるリスクが高そうで、保守が大仕事になりそうだ
例えば、2年後にもコンパイルは問題なくできるのだろうか
jqは非常に強力なツールだが、最近はDuckDBもよく使われている
データがある程度表形式なら、SQLのほうがはるかに自然な言語だ
C#のLINQに少し似ているが、SQLのほうがより標準化されているので好みだ
言語内で生のコレクションをSQLでクエリできると素晴らしいし、さらにコレクションを透過的にSqliteへ保存できるならもっとよい
データベースなどからデータを取得したあと、ループやストリームAPIで単純に処理するコードを見ると、いつも惜しいと感じる。この用途では、SQLはJava/Kotlin/Python/JavaScriptよりはるかに高水準で簡潔だ
元のJSON出力をすべてsqliteテーブルに保存し、そこで仮想カラムを作ってから、
select結果をシェルのループで回しているネストしたループが解消され、正確なレコードをDBで確認して再実行できるので、デバッグ可能性がずっと良くなる
作っているものがDAGだと気づき、常に最後に成功処理されたレコードから再開している。これを表現できる
Makeのようなツールがあるのか気になっているMakeにはSQLターゲットがなく、Airflowのような本格的なDAG処理系はシェル片をつなげるには重すぎる
それでも、SQLで再帰クエリを簡潔に表現する方法は依然としてなかなか得られない
https://github.com/dinedal/textql
正確性の観点では、uint64数値を欠けることなく表示できるのか気になる
いまのjqでいちばん気になる点だ
ただし最新仕様であるRFC 8259は、数値のテキスト形式だけを規定し、意味論は定めないと修正している
実際にはほとんどの実装がJSONをJavaScriptのサブセットのように扱うため、数値は64ビット浮動小数点だという前提につながっている
10進数の数値リテラルを使って精度を保持し、比較演算は精度を尊重するが、算術演算では切り詰められる可能性がある、とされている
現在はdecimal64に切り詰めるので少し紛らわしいが、次のリリースではJSON仕様の提案に合わせてbinary64(double) に切り詰めるよう修正される予定だ: https://github.com/jqlang/jq/pull/2949
jlessに乗り換えてから振り返っていない
ユーザーインターフェースが他のものよりはるかに先を行っている
jqは単なるビューアではなく、JSONクエリ言語プロセッサだ
Rustのどこかにターミナルのラインアート用ライブラリがあるのはかわいいが、jaqを実行してみたところ、iTermにエスケープコードをメガバイト単位で吐き出し、最終的にiTermがプリンタへ出力しようとした
賢く振る舞いすぎていた
echo *json | rush -- jaq -rf ./this-program.jq {} | datamash ...のような文脈では、TTYに芸術的な効果を入れようとするのは適切ではないと思うエラーの原因は、いずれにせよ
jaqに**strftime** がないことだった第一印象は、格好いいエラーメッセージはあるが**
halt_error/0** がない、というものだhalt_errorをコメントアウトしたあとは、jqやgojqより遅かった同じ入力で、
jqは約0.023秒、gojqは約0.070秒、jaqは約0.103秒かかった使用した
aoc22-13.jqはhttps://pastebin.com/raw/YiUjEu2nで、input.txtはhttps://pastebin.com/raw/X0FSyTNfだjqの代わりにyqを使い始めたのだが、重要な違いがあるのか気になる
個人的にはhttps://github.com/mikefarah/yqをhttps://github.com/kislyuk/yqより好む
YAMLの処理がJSONよりはるかに難しいことは理解しているが、yqはバージョン3から4にかけて構文をjqに近づけたにもかかわらず、どういうわけか完全には同じではない
またyqにはif-then-elseがないため、設計がよくないか、抜け落ちているように見える: https://github.com/mikefarah/yq/issues/95
YAMLを処理する必要があるときはyqはうまく動き、コメントもかなりうまく扱うが、純粋なJSON処理にはjqのほうが優れたツールだ