深い階層のJSONでjqに迷子にならないよう、スキーマを見ながら試せるアプリをClaude Codeに作ってもらった
はじめに
APIのレスポンスやログを jq で絞り込むことはよくあります。しかし JSON の階層が深くなると、クエリを書く前に「どこが配列で、どこがオブジェクトか」をまず把握しなければならず、途端にややこしくなります。
expected an object but got: array (...)
.orders.items と書いてこう言われ、JSON を見に戻り、[] を足してもう一度…。そうやってターミナルとエディタを往復した経験のある方も多いと思います。
そこで、JSONの構造(スキーマ)を横に表示したまま、jqクエリを書き換えるたびに結果がすぐ出るアプリを、Claude Code に作ってもらいました。
この記事では、
1. 深い階層のJSONを扱うための jq の基本
2. 作ったアプリ「shiboq」で工夫した点
3. Claude Code と一緒に作ってみて感じたこと
を紹介します。
作ったもの: shiboq
shiboq(しぼきゅー)は、「絞る」と jq の「q」を合わせた名前です。
!shiboq の画面。左にサンプル、中央にJSONとスキーマ、右にクエリと実行結果
| 版 | 入手先 |
|---|---|
| Web版(インストール不要) | shiboq.e17.click |
| Windows版 | Microsoft Store |
| ソースコード | morststs/shiboq (MIT License) |
画面は5つのペインに分かれています。
| 左 | 中央 | 右 |
|---|---|---|
| 保存 / サンプル<br>選ぶとJSONとクエリを復元 | JSON(上)<br>貼り付け・編集・整形<br><br>スキーマ(下)<br>キーと型のツリー<br>使用中のキーをハイライト | jqクエリ(上)<br>キー名と関数を補完<br><br>実行結果(下)<br>エラー中は前回の結果を表示 |
JSON を貼り付けてクエリを書くと、入力が止まって 500ms 後に自動で実行されます。実行ボタンはありません。
:::note info
Web版はjqエンジンを WebAssembly にしてブラウザ内で動かしているので、貼り付けたJSONはどこにも送信されません。業務のJSONでも試しやすいようにしました。
:::
jqの基本(深い階層のJSONで)
以下、次のような「お店の商品と注文」のJSONを例にします(shiboq の学習用サンプルに入っているものです)。
{
"store": "しぼQ商店",
"products": [
{ "id": 1, "name": "りんご", "category": "果物", "price": 120, "stock": 30, "tags": ["国産", "旬"] },
{ "id": 2, "name": "バナナ", "category": "果物", "price": 98, "stock": 0, "tags": ["輸入"] }
// …全5件
],
"orders": [
{
"id": "A-001",
"customer": { "name": "佐藤", "email": "sato@example.com" },
"items": [ { "productId": 1, "qty": 3 }, { "productId": 4, "qty": 1 } ],
"status": "shipped"
},
{
"id": "A-002",
"customer": { "name": "鈴木", "email": null },
"items": [ { "productId": 5, "qty": 2 } ],
"status": "pending"
}
// …全3件
]
}
shiboq のスキーマペインでは、これが次のように表示されます。
store: string
products: array<object>
id: number
name: string
category: string
price: number
stock: number
tags: array<string>
orders: array<object>
id: string
customer: object
name: string
email: string
items: array<object>
productId: number
qty: number
status: string
jq で迷うのは、たいてい「array と object の境目」です。それを頭に置いて見ていきます。
1. 値を取り出す . と .key
.store
# => "しぼQ商店"
.orders[0].customer.name
# => "佐藤"
. は「いま手元にある値」です。オブジェクトは .キー名、配列は [添字] でたどります。
2. 配列を展開する .[]
.products[].name
# => "りんご"
# "バナナ"
# "にんじん"
# "牛乳"
# "チーズ"
[] は配列の要素を 1つずつ別々の結果として 出します。出力が5つになっていることに注意してください。
ここが、冒頭のエラーの正体です。
.orders.items
# => エラー: expected an object but got: array (...)
スキーマを見ると orders: array<object> です。配列に .items は無いので、array の後ろには [] が必要です。
.orders[].items[].productId
# => 1 4 5 2 3 1
深いJSONでは「スキーマで array と書いてある階層には [] を付ける」と覚えておくと、迷う回数がかなり減ります。
3. パイプ | でつなぐ
.orders[] | .customer | .name
| は「左の結果を1つずつ右に渡す」です。.orders[].customer.name と同じ意味ですが、長くなったらパイプで区切ったほうが読みやすくなります。
4. 結果を配列にまとめる [ ]、オブジェクトを組み立てる { }
[.products[].name]
# => ["りんご","バナナ","にんじん","牛乳","チーズ"]
.products[] | {name, price}
# => {"name":"りんご","price":120}
# {"name":"バナナ","price":98} …
.[] でバラバラになったものを [ ] で囲むと、配列に戻せます。{name, price} は {name: .name, price: .price} の省略形です。
深い階層から必要な値だけを平らにして取り出すときは、これがいちばん使えます。
.orders[] | {id, name: .customer.name, "点数": (.items | map(.qty) | add)}
# => {"id":"A-001","name":"佐藤","点数":4}
# {"id":"A-002","name":"鈴木","点数":2}
# {"id":"A-003","name":"高橋","点数":9}
:::note warn
キー名に日本語を使うときは "点数" のように引用符が必要です。{点数: ...} や .会員 は構文エラーになります。取り出すときは ."会員" または .["会員"] と書きます。
:::
5. 絞り込む select
.products[] | select(.stock == 0) | .name
# => "バナナ"
.orders[] | select(.customer.email == null) | .id
# => "A-002"
select(条件) は、条件が真のものだけを通します。
6. 集計する map / add / group_by
[.orders[].items[].qty] | add
# => 15
.products | group_by(.category) | map({category: .[0].category, "件数": length})
# => [{"category":"乳製品","件数":2},{"category":"果物","件数":2},{"category":"野菜","件数":1}]
「2階層下の値を全部集めて合計する」は、[ ... ] で集めてから add すると書けます。
7. 別の配列と突き合わせる as $変数 と INDEX
注文の items には productId しか無く、値段は products 側にあります。こういうときは、先に products を id で引ける形にして変数に入れておきます。
INDEX(.products[]; .id) as $p
| .orders[]
| {id, "合計": (.items | map($p[.productId | tostring].price * .qty) | add)}
# => {"id":"A-001","合計":570}
# {"id":"A-002","合計":900}
# {"id":"A-003","合計":868}
.orders[] に入ったあとでは . が注文1件になってしまい、.products を参照できません。階層を下りる前に as $p で取っておくのがコツです。
8. どこにあるか分からないキーを探す ..
[.. | .email? // empty]
# => ["sato@example.com","takahashi@example.org"]
.. はすべての階層を再帰的にたどります。「email がどこかにあるはずだけど階層が分からない」というときの最初の一手に便利です。
深い階層でハマりやすいところ
| 症状 | 原因 | 対処 |
|---|---|---|
| expected an object but got: array | 配列に .key を付けた | [] を足す |
| cannot iterate over: null | 存在しないキーに [] を付けた | キー名の綴りを確認/.foo[]? |
| エラーにならず null が出る | キー名の打ち間違い | スキーマで実在するキーか確認する |
最後の「null が出るだけでエラーにならない」が厄介です。jq は存在しないキーを参照しても null を返すので、.custmer.name のような打ち間違いに気づきにくいのです。
これが「スキーマを横に置いて、いま参照しているキーをハイライトしたい」と思った一番の理由です。
アプリで工夫した点
1. スキーマ表示と「使用中のキー」のハイライト
スキーマペインでは、いまのクエリが参照しているキーに「使用中」バッジが付きます。
.orders[] | {id, name: .customer.name, "点数": (.items | map(.qty) | add)} のときは、次のようになります。
!クエリで使っている orders・customer・name・id・items・qty がハイライトされたスキーマ
打ち間違えたキーはハイライトされません。.orders[] | .custmer.name(customer の綴り間違い)と書くと、結果は null が並ぶだけですが、スキーマを見ると customer が光っていないので、綴りのせいだとすぐに分かります。
!綴りを間違えた customer はハイライトされていないスキーマ
:::note
判定はキー名の一致だけで行っていて、どの階層のキーかまでは区別していません。そのため上の画像でも、クエリでは使っていない products の id や name まで光っています。
:::
参照しているキーは、正規表現で拾うのではなく、jqエンジン(gojq)でクエリをパースした構文木(AST)から取り出しています。そのため、次のような書き方の中のキーも拾えます。
"\(.name)様" # 文字列補間の中
-.price # 単項演算子
. as {customer: {name: $n}} | $n # 分解パターン
."会員" / .["会員"] # 引用符付き・ブラケット記法
一方で、.["\(.k)_id"] のように実行時にならないと決まらないキー名は対象外にし、補間の中の .k だけを拾うようにしています。
2. キー名の補完
クエリペインでは、いま読み込んでいるJSONに含まれるキー名と、よく使う組み込み関数(map・select・keys など)が補完候補に出ます。エディタは VS Code と同じ Monaco Editor です。
3. エラーになっても前回の結果を消さない
自動実行なので、.orders[ のように書いている途中のクエリは当然エラーになります。そのたびに結果が消えると、画面がちらついて落ち着きません。
そこで、エラーのときは前回成功したときの結果を残し、「前回の成功結果」バッジを付けて薄く表示するようにしました。
!.orders.items で実行エラーになり、結果ペインに「前回の成功結果」バッジ付きで直前の結果が薄く残っている
また、エラーの出し分けもしています。
- JSON自体が壊れている → JSON/スキーマ側にだけエラーを出す(クエリペインには出さない)
- クエリの構文エラー → 構文エラー: …
- 未定義の関数など → コンパイルエラー: …
JSON の閉じカッコを消しただけなのにクエリ側までエラーで真っ赤になる、ということが起きないようにしています。
4. 書きかけのクエリが暴走しないように
自動実行には怖い面もあります。たとえば range(1e9) や repeat(.) は、書いている途中に一瞬でも成立すると、終わらない処理が始まってしまいます。
そのため、実行には次の上限を設けています。
- 実行時間 5秒でタイムアウト
- 出力 10,000件、または 10MB で打ち切り
Web版はさらに一工夫が必要でした。WebAssembly の Go には goroutine のプリエンプション(割り込み)が無く、def f: f; f のような CPU を握り続けるクエリでは Go側のタイムアウトが発火しません。そこで、jqエンジンを Web Worker で動かし、時間切れになったら Worker ごと terminate() するようにしました。新しいクエリが来たときに前のクエリがまだ動いていれば、それも Worker ごと止めます。
5. 「履歴」をやめて「明示的な保存」にした
最初は「実行に成功するたびに自動で履歴に残す」作りでした。ところが使ってみると、履歴が次のように埋まってしまいました。
.a
.add
.addr
.addre
.address
jq は存在しないキーでもエラーにせず null を返すので、入力途中のクエリがほぼすべて「成功」として残ってしまったのです。
そこで自動履歴はやめ、Ctrl+S(または「+ 保存」ボタン)で自分で保存したものだけが残るようにしました。名前は空でもよく、空のときはクエリがそのまま見出しになります。
また、エラー中は保存できないようにしています。表示中の結果は「前回の成功結果」なので、いまのクエリと食い違ったまま保存されてしまうからです。
6. 学習用サンプル(10章・43問)とヘルプ
左ペインの「サンプル」タブに、この記事で紹介したような例題を10章・43問入れました。選ぶとJSONとクエリが読み込まれ、解説と「やってみよう」を見ながらその場で書き換えられます。
1. 値を取り出す
2. 配列を展開する
3. オブジェクトを組み立てる
4. 条件で絞り込む
5. 既定値・条件分岐・並べ替え
6. 集計する
7. 変数・reduce・関数
8. 文字列と正規表現
9. 出力の形式(@csv など)
10. 更新と探索(|=・del・..・paths)
サンプルはすべて、テストで実際のjqエンジンに通して、エラーにならないこと・結果が空でないことを確認しています。サンプルを作っている途中で、日本語のキーを引用符なしで書いた例が軒並み構文エラーになり、このテストで気づきました(前述の「日本語キーには引用符が必要」は、こうして身をもって学んだことです)。
ヘルプには、構文の早見表と「困ったとき」のQ&Aのほか、使えない機能も書いています。shiboq は jq 本体ではなく gojq を内蔵しているため、jq と少し違うところがあるからです。
- jq 1.7 の仕様に準拠(trim・toboolean など jq 1.8 で追加された関数の一部も使える)
- input / inputs・debug・$__loc__ などは使えない
- 正規表現のフラグは g / i / m のみ(Go の regexp を使っているため)
- エラーメッセージの文面は jq と異なる
7. その他の細かいところ
- 生出力(-r 相当)の切り替え: @csv や @tsv の結果をそのまま読めます。
- JSONの整形ボタン: JSON.parse → JSON.stringify ではなく、エディタのフォーマッタで整形します。前者だと大きな整数の精度が落ちたり 1.0 が 1 になったりするうえ、Ctrl+Z で戻せないためです。
- スキーマを JSON Schema としてコピー: 推論したスキーマを type / properties / items だけの JSON Schema にしてクリップボードへ。1件のサンプルからは判断できない required などは付けません。
- 外部への通信をしない: Webフォントは CDN から読み込まず、OS のフォントを使います。Microsoft Store の公式バッジも外部スクリプトを読み込むため使わず、普通のリンクにしました。
- jqのインストール不要: gojq を組み込んでいるので、jq コマンドが入っていない環境でも動きます。
技術構成
| | |
|---|---|
| デスクトップアプリ | Wails v2(Go) |
| jqエンジン | itchyny/gojq(純Go実装) |
| フロントエンド | Svelte 5(runes)+ Vite |
| エディタ | Monaco Editor |
| UI | Flowbite Svelte + Tailwind CSS 4 |
| Web版 | 同じGoコードを WebAssembly にビルドし、GitHub Pages で公開 |
Web版とデスクトップ版は同じ Svelte のコードです。Vite のエイリアスで、呼び出し先を「Wailsのバインディング」か「WebAssemblyを動かすWorker」に切り替えています。jqの実行やスキーマ推論のGoコードもそのまま共用しています。
Claude Code に作ってもらって
進め方
最初に「jqを繰り返し実行して結果を確認できるアプリが欲しい」という要望と、参考にしたい自作アプリ(同じ Wails + Svelte 構成のメモアプリ)を伝え、Claude Code と相談しながら設計書と実装計画書を作りました。そのうえで実装してもらい、使ってみて気になった点を直してもらう、の繰り返しです。
開発環境は、ホストに Go や Node を入れず、すべて Docker コンテナの中でビルド・テストする構成にしました。Windows 版の exe も、コンテナ内の mingw-w64 でクロスコンパイルしています。
よかったこと
- 「なぜそうしたか」を CLAUDE.md に残してくれる: 「履歴をやめて保存にした理由」「整形で JSON.parse を使わない理由」などが、設計判断として CLAUDE.md に書き足されていきます。次のセッションで Claude Code が同じ失敗を繰り返さず、自分で読み返しても経緯が分かります。
- テストで確かめてくれる: 前述のサンプル全件テストのように、「動くはず」ではなく実際に動かして確かめる仕組みを作ってくれました。Web版も、ヘッドレスブラウザで画面を操作して動作確認しています。
- 実装以外も手伝ってくれる: GitHub Actions でのビルド、GitHub Pages への公開、Microsoft Store 提出用の MSIX パッケージ作成、プライバシーポリシーやストアの説明文の下書きまで手伝ってもらいました。
人間が決めたこと
一方で、次のような判断は自分で行いました。
- 自動履歴をやめて明示的な保存にする
- 外部スクリプトを読み込む公式バッジは使わない
- Store への申請は全自動ではなく、半自動にとどめる
また、Windows の実機での確認(スマート アプリ コントロールに未署名の exe がブロックされた件など)も自分で行いました。この件がきっかけで、Microsoft が署名してくれる Store 経由での配布を選んでいます。
<!-- ここに感想を一言(例: どれくらいの期間でできたか、意外だったことなど) -->
おわりに
深い階層のJSONを jq で扱うときのコツは、
- スキーマで array の階層を確認して [] を付ける
- 階層を下りる前に、あとで使う値を as $x で取っておく
- null が出たら、まずキー名の綴りを疑う
の3つだと思っています。shiboq は、この3つを「目で見て確かめながら」試せるように作りました。
Web版はインストール不要ですぐ試せます。jq の練習にも、手元のJSONの調査にも使ってみてください。
shiboq.e17.click
2 likes
Mentions
深い階層のJSONでjqに迷子にならないよう、スキーマを見ながら試せるアプリをClaude Codeに作ってもらった はじめに APIのレスポンスやログを jq で絞り込むことはよくあります。しかし JSON の階層が深くなると、クエリを書く前に「どこが配列で、どこがオブジェクトか」をまず把握しなければならず、途端にややこしくなります。 expected an object but got: array (...) .orders.items と書いてこう言われ、JSON を見に戻り、[] を足してもう一度…。そうやってターミナルとエディタを往復した経験のある方も多いと思います。 そこで、JSONの構造(スキーマ)を横に表示したまま、jqクエリを書き換えるたびに結果がすぐ出るアプリを、Claude Code に作ってもらいました。 この記事では、 1. 深い階層のJSONを扱うための jq の基本 2. 作ったアプリ「shiboq」で工夫した点 3. Claude Code と一緒に作ってみて感じたこと を紹介します。 作ったもの: shiboq shiboq(しぼきゅー)は、「絞る」と jq の「q」を合わせた名前です。 !shiboq の画面。左にサンプル、中央にJSONとスキーマ、右にクエリと実行結果 | 版 | 入手先 | |---|---| | Web版(インストール不要) | shiboq.e17.click | | Windows版 | Microsoft Store | | ソースコード | morststs/shiboq (MIT License) | 画面は5つのペインに分かれています。 | 左 | 中央 | 右 | |---|---|---| | 保存 / サンプル<br>選ぶとJSONとクエリを復元 | JSON(上)<br>貼り付け・編集・整形<br><br>スキーマ(下)<br>キーと型のツリー<br>使用中のキーをハイライト | jqクエリ(上)<br>キー名と関数を補完<br><br>実行結果(下)<br>エラー中は前回の結果を表示 | JSON を貼り付けてクエリを書くと、入力が止まって 500ms 後に自動で実行されます。実行ボタンはありません。 :::note info Web版はjqエンジンを WebAssembly にしてブラウザ内で動かしているので、貼り付けたJSONはどこにも送信されません。業務のJSONでも試しやすいようにしました。 ::: jqの基本(深い階層のJSONで) 以下、次のような「お店の商品と注文」のJSONを例にします(shiboq の学習用サンプルに入っているものです)。 { "store": "しぼQ商店", "products": [ { "id": 1, "name": "りんご", "category": "果物", "price": 120, "stock": 30, "tags": ["国産", "旬"] }, { "id": 2, "name": "バナナ", "category": "果物", "price": 98, "stock": 0, "tags": ["輸入"] } // …全5件 ], "orders": [ { "id": "A-001", "customer": { "name": "佐藤", "email": "sato@example.com" }, "items": [ { "productId": 1, "qty": 3 }, { "productId": 4, "qty": 1 } ], "status": "shipped" }, { "id": "A-002", "customer": { "name": "鈴木", "email": null }, "items": [ { "productId": 5, "qty": 2 } ], "status": "pending" } // …全3件 ] } shiboq のスキーマペインでは、これが次のように表示されます。 store: string products: array<object> id: number name: string category: string price: number stock: number tags: array<string> orders: array<object> id: string customer: object name: string email: string items: array<object> productId: number qty: number status: string jq で迷うのは、たいてい「array と object の境目」です。それを頭に置いて見ていきます。 1. 値を取り出す . と .key .store # => "しぼQ商店" .orders[0].customer.name # => "佐藤" . は「いま手元にある値」です。オブジェクトは .キー名、配列は [添字] でたどります。 2. 配列を展開する .[] .products[].name # => "りんご" # "バナナ" # "にんじん" # "牛乳" # "チーズ" [] は配列の要素を 1つずつ別々の結果として 出します。出力が5つになっていることに注意してください。 ここが、冒頭のエラーの正体です。 .orders.items # => エラー: expected an object but got: array (...) スキーマを見ると orders: array<object> です。配列に .items は無いので、array の後ろには [] が必要です。 .orders[].items[].productId # => 1 4 5 2 3 1 深いJSONでは「スキーマで array と書いてある階層には [] を付ける」と覚えておくと、迷う回数がかなり減ります。 3. パイプ | でつなぐ .orders[] | .customer | .name | は「左の結果を1つずつ右に渡す」です。.orders[].customer.name と同じ意味ですが、長くなったらパイプで区切ったほうが読みやすくなります。 4. 結果を配列にまとめる [ ]、オブジェクトを組み立てる { } [.products[].name] # => ["りんご","バナナ","にんじん","牛乳","チーズ"] .products[] | {name, price} # => {"name":"りんご","price":120} # {"name":"バナナ","price":98} … .[] でバラバラになったものを [ ] で囲むと、配列に戻せます。{name, price} は {name: .name, price: .price} の省略形です。 深い階層から必要な値だけを平らにして取り出すときは、これがいちばん使えます。 .orders[] | {id, name: .customer.name, "点数": (.items | map(.qty) | add)} # => {"id":"A-001","name":"佐藤","点数":4} # {"id":"A-002","name":"鈴木","点数":2} # {"id":"A-003","name":"高橋","点数":9} :::note warn キー名に日本語を使うときは "点数" のように引用符が必要です。{点数: ...} や .会員 は構文エラーになります。取り出すときは ."会員" または .["会員"] と書きます。 ::: 5. 絞り込む select .products[] | select(.stock == 0) | .name # => "バナナ" .orders[] | select(.customer.email == null) | .id # => "A-002" select(条件) は、条件が真のものだけを通します。 6. 集計する map / add / group_by [.orders[].items[].qty] | add # => 15 .products | group_by(.category) | map({category: .[0].category, "件数": length}) # => [{"category":"乳製品","件数":2},{"category":"果物","件数":2},{"category":"野菜","件数":1}] 「2階層下の値を全部集めて合計する」は、[ ... ] で集めてから add すると書けます。 7. 別の配列と突き合わせる as $変数 と INDEX 注文の items には productId しか無く、値段は products 側にあります。こういうときは、先に products を id で引ける形にして変数に入れておきます。 INDEX(.products[]; .id) as $p | .orders[] | {id, "合計": (.items | map($p[.productId | tostring].price * .qty) | add)} # => {"id":"A-001","合計":570} # {"id":"A-002","合計":900} # {"id":"A-003","合計":868} .orders[] に入ったあとでは . が注文1件になってしまい、.products を参照できません。階層を下りる前に as $p で取っておくのがコツです。 8. どこにあるか分からないキーを探す .. [.. | .email? // empty] # => ["sato@example.com","takahashi@example.org"] .. はすべての階層を再帰的にたどります。「email がどこかにあるはずだけど階層が分からない」というときの最初の一手に便利です。 深い階層でハマりやすいところ | 症状 | 原因 | 対処 | |---|---|---| | expected an object but got: array | 配列に .key を付けた | [] を足す | | cannot iterate over: null | 存在しないキーに [] を付けた | キー名の綴りを確認/.foo[]? | | エラーにならず null が出る | キー名の打ち間違い | スキーマで実在するキーか確認する | 最後の「null が出るだけでエラーにならない」が厄介です。jq は存在しないキーを参照しても null を返すので、.custmer.name のような打ち間違いに気づきにくいのです。 これが「スキーマを横に置いて、いま参照しているキーをハイライトしたい」と思った一番の理由です。 アプリで工夫した点 1. スキーマ表示と「使用中のキー」のハイライト スキーマペインでは、いまのクエリが参照しているキーに「使用中」バッジが付きます。 .orders[] | {id, name: .customer.name, "点数": (.items | map(.qty) | add)} のときは、次のようになります。 !クエリで使っている orders・customer・name・id・items・qty がハイライトされたスキーマ 打ち間違えたキーはハイライトされません。.orders[] | .custmer.name(customer の綴り間違い)と書くと、結果は null が並ぶだけですが、スキーマを見ると customer が光っていないので、綴りのせいだとすぐに分かります。 !綴りを間違えた customer はハイライトされていないスキーマ :::note 判定はキー名の一致だけで行っていて、どの階層のキーかまでは区別していません。そのため上の画像でも、クエリでは使っていない products の id や name まで光っています。 ::: 参照しているキーは、正規表現で拾うのではなく、jqエンジン(gojq)でクエリをパースした構文木(AST)から取り出しています。そのため、次のような書き方の中のキーも拾えます。 "\(.name)様" # 文字列補間の中 -.price # 単項演算子 . as {customer: {name: $n}} | $n # 分解パターン ."会員" / .["会員"] # 引用符付き・ブラケット記法 一方で、.["\(.k)_id"] のように実行時にならないと決まらないキー名は対象外にし、補間の中の .k だけを拾うようにしています。 2. キー名の補完 クエリペインでは、いま読み込んでいるJSONに含まれるキー名と、よく使う組み込み関数(map・select・keys など)が補完候補に出ます。エディタは VS Code と同じ Monaco Editor です。 3. エラーになっても前回の結果を消さない 自動実行なので、.orders[ のように書いている途中のクエリは当然エラーになります。そのたびに結果が消えると、画面がちらついて落ち着きません。 そこで、エラーのときは前回成功したときの結果を残し、「前回の成功結果」バッジを付けて薄く表示するようにしました。 !.orders.items で実行エラーになり、結果ペインに「前回の成功結果」バッジ付きで直前の結果が薄く残っている また、エラーの出し分けもしています。 - JSON自体が壊れている → JSON/スキーマ側にだけエラーを出す(クエリペインには出さない) - クエリの構文エラー → 構文エラー: … - 未定義の関数など → コンパイルエラー: … JSON の閉じカッコを消しただけなのにクエリ側までエラーで真っ赤になる、ということが起きないようにしています。 4. 書きかけのクエリが暴走しないように 自動実行には怖い面もあります。たとえば range(1e9) や repeat(.) は、書いている途中に一瞬でも成立すると、終わらない処理が始まってしまいます。 そのため、実行には次の上限を設けています。 - 実行時間 5秒でタイムアウト - 出力 10,000件、または 10MB で打ち切り Web版はさらに一工夫が必要でした。WebAssembly の Go には goroutine のプリエンプション(割り込み)が無く、def f: f; f のような CPU を握り続けるクエリでは Go側のタイムアウトが発火しません。そこで、jqエンジンを Web Worker で動かし、時間切れになったら Worker ごと terminate() するようにしました。新しいクエリが来たときに前のクエリがまだ動いていれば、それも Worker ごと止めます。 5. 「履歴」をやめて「明示的な保存」にした 最初は「実行に成功するたびに自動で履歴に残す」作りでした。ところが使ってみると、履歴が次のように埋まってしまいました。 .a .add .addr .addre .address jq は存在しないキーでもエラーにせず null を返すので、入力途中のクエリがほぼすべて「成功」として残ってしまったのです。 そこで自動履歴はやめ、Ctrl+S(または「+ 保存」ボタン)で自分で保存したものだけが残るようにしました。名前は空でもよく、空のときはクエリがそのまま見出しになります。 また、エラー中は保存できないようにしています。表示中の結果は「前回の成功結果」なので、いまのクエリと食い違ったまま保存されてしまうからです。 6. 学習用サンプル(10章・43問)とヘルプ 左ペインの「サンプル」タブに、この記事で紹介したような例題を10章・43問入れました。選ぶとJSONとクエリが読み込まれ、解説と「やってみよう」を見ながらその場で書き換えられます。 1. 値を取り出す 2. 配列を展開する 3. オブジェクトを組み立てる 4. 条件で絞り込む 5. 既定値・条件分岐・並べ替え 6. 集計する 7. 変数・reduce・関数 8. 文字列と正規表現 9. 出力の形式(@csv など) 10. 更新と探索(|=・del・..・paths) サンプルはすべて、テストで実際のjqエンジンに通して、エラーにならないこと・結果が空でないことを確認しています。サンプルを作っている途中で、日本語のキーを引用符なしで書いた例が軒並み構文エラーになり、このテストで気づきました(前述の「日本語キーには引用符が必要」は、こうして身をもって学んだことです)。 ヘルプには、構文の早見表と「困ったとき」のQ&Aのほか、使えない機能も書いています。shiboq は jq 本体ではなく gojq を内蔵しているため、jq と少し違うところがあるからです。 - jq 1.7 の仕様に準拠(trim・toboolean など jq 1.8 で追加された関数の一部も使える) - input / inputs・debug・$__loc__ などは使えない - 正規表現のフラグは g / i / m のみ(Go の regexp を使っているため) - エラーメッセージの文面は jq と異なる 7. その他の細かいところ - 生出力(-r 相当)の切り替え: @csv や @tsv の結果をそのまま読めます。 - JSONの整形ボタン: JSON.parse → JSON.stringify ではなく、エディタのフォーマッタで整形します。前者だと大きな整数の精度が落ちたり 1.0 が 1 になったりするうえ、Ctrl+Z で戻せないためです。 - スキーマを JSON Schema としてコピー: 推論したスキーマを type / properties / items だけの JSON Schema にしてクリップボードへ。1件のサンプルからは判断できない required などは付けません。 - 外部への通信をしない: Webフォントは CDN から読み込まず、OS のフォントを使います。Microsoft Store の公式バッジも外部スクリプトを読み込むため使わず、普通のリンクにしました。 - jqのインストール不要: gojq を組み込んでいるので、jq コマンドが入っていない環境でも動きます。 技術構成 | | | |---|---| | デスクトップアプリ | Wails v2(Go) | | jqエンジン | itchyny/gojq(純Go実装) | | フロントエンド | Svelte 5(runes)+ Vite | | エディタ | Monaco Editor | | UI | Flowbite Svelte + Tailwind CSS 4 | | Web版 | 同じGoコードを WebAssembly にビルドし、GitHub Pages で公開 | Web版とデスクトップ版は同じ Svelte のコードです。Vite のエイリアスで、呼び出し先を「Wailsのバインディング」か「WebAssemblyを動かすWorker」に切り替えています。jqの実行やスキーマ推論のGoコードもそのまま共用しています。 Claude Code に作ってもらって 進め方 最初に「jqを繰り返し実行して結果を確認できるアプリが欲しい」という要望と、参考にしたい自作アプリ(同じ Wails + Svelte 構成のメモアプリ)を伝え、Claude Code と相談しながら設計書と実装計画書を作りました。そのうえで実装してもらい、使ってみて気になった点を直してもらう、の繰り返しです。 開発環境は、ホストに Go や Node を入れず、すべて Docker コンテナの中でビルド・テストする構成にしました。Windows 版の exe も、コンテナ内の mingw-w64 でクロスコンパイルしています。 よかったこと - 「なぜそうしたか」を CLAUDE.md に残してくれる: 「履歴をやめて保存にした理由」「整形で JSON.parse を使わない理由」などが、設計判断として CLAUDE.md に書き足されていきます。次のセッションで Claude Code が同じ失敗を繰り返さず、自分で読み返しても経緯が分かります。 - テストで確かめてくれる: 前述のサンプル全件テストのように、「動くはず」ではなく実際に動かして確かめる仕組みを作ってくれました。Web版も、ヘッドレスブラウザで画面を操作して動作確認しています。 - 実装以外も手伝ってくれる: GitHub Actions でのビルド、GitHub Pages への公開、Microsoft Store 提出用の MSIX パッケージ作成、プライバシーポリシーやストアの説明文の下書きまで手伝ってもらいました。 人間が決めたこと 一方で、次のような判断は自分で行いました。 - 自動履歴をやめて明示的な保存にする - 外部スクリプトを読み込む公式バッジは使わない - Store への申請は全自動ではなく、半自動にとどめる また、Windows の実機での確認(スマート アプリ コントロールに未署名の exe がブロックされた件など)も自分で行いました。この件がきっかけで、Microsoft が署名してくれる Store 経由での配布を選んでいます。 <!-- ここに感想を一言(例: どれくらいの期間でできたか、意外だったことなど) --> おわりに 深い階層のJSONを jq で扱うときのコツは、 - スキーマで array の階層を確認して [] を付ける - 階層を下りる前に、あとで使う値を as $x で取っておく - null が出たら、まずキー名の綴りを疑う の3つだと思っています。shiboq は、この3つを「目で見て確かめながら」試せるように作りました。 Web版はインストール不要ですぐ試せます。jq の練習にも、手元のJSONの調査にも使ってみてください。 shiboq.e17.click