jq 1行文 CLIでスクリプトを書かずにJSONをフィルタおよび変換
バックエンド開発者およびDevOps向けの7つの実用的なjqレシピ — select、map、to_entries、del、group_by、実際のAPIリシェイプをカバー。すべてのパターンに対して入力と出力が示されています。
あなたはターミナルで最小化されたAPIレスポンスを見ています。3つのフィールドを抽出し、nullをフィルタリングし、結果を他のコマンドにパイプする必要があります。これにPythonスクリプトを書くのは20行で、2分も時間がないのです。 jq パターンを知っているなら、1つのコマンドで処理できます。
以下の7つのレシピ。それぞれがAPIレスポンスやログファイルを扱う際に繰り返し使うパターンをカバーしています。すべてのレシピには入力、コマンド、出力が示されています。
インストール
brew install jq # macOS
apt-get install jq # Ubuntu/Debian
apk add jq # Alpine (Docker images)
レシピ1: 配列内のすべてのオブジェクトからフィールドを抽出
最もよく行われることです。 jq: JSON配列内のすべてのアイテムから1つのフィールドを取得します。
入力 (users.json)
[
{"id": 1, "name": "Alice", "email": "alice@example.com", "role": "admin"},
{"id": 2, "name": "Bob", "email": "bob@example.com", "role": "user"},
{"id": 3, "name": "Carol", "email": "carol@example.com", "role": "user"}
]
jq '.[].name' users.json
# → "Alice"
# "Bob"
# "Carol" (newline-separated stream)
.[] 配列内のすべての要素を繰り返します; .name フィールドを取得します。出力はストリームです — 他のコマンドにパイプするのに便利です。適切なJSON配列を必要とする場合:
jq 'map(.name)' users.json
# → ["Alice", "Bob", "Carol"]
map(.name) は、以下のプロパティのショートハンドです。 [.[] | .name] — 各アイテムに対して式を適用し、結果を配列に包みます。これはよく使われます。 map() よく使われます。
レシピ2: を使用してフィルタリング select
条件に一致するオブジェクトだけを残し、残りを削除します。
jq 'map(select(.role == "admin"))' users.json
[
{"id": 1, "name": "Alice", "email": "alice@example.com", "role": "admin"}
]
select(expr) 式が真の場合にアイテムを通過させます — それ以外は消えます。 を包むことで出力が配列として保持されます。さらに例: map() keeps the output as an array. More examples:
# Numeric comparison
jq 'map(select(.score > 80))' results.json
# Not-null check
jq 'map(select(.error != null))' events.json
# String contains (requires test)
jq 'map(select(.message | test("timeout")))' logs.json
レシピ3: を使用してオブジェクトを再構成 map
必要なフィールドだけを選び、必要に応じて同じ処理で名前を変更します。
jq 'map({name: .name, role: .role})' users.json
[
{"name": "Alice", "role": "admin"},
{"name": "Bob", "role": "user"},
{"name": "Carol", "role": "user"}
]
各アイテムに対して新しいオブジェクトリテラルを作成します。キーをリネームしたい場合は、コロンの左側を変更します:
jq 'map({username: .name, access_level: .role})' users.json
1つのショートカットでキー入力を節約できます: {name} は、名前 {name: .name}。フィールドをそのまま使う場合は、キーを2回書く必要はありません:
jq 'map({id, name, role})' users.json
レシピ4: を使用してキーをリネーム to_entries と from_entries
map キーが存在することを知っている場合に有効です。キー自体を変換する必要がある場合 — プレフィックスを追加、名前規則を変換、またはマップに基づいてリネームする場合 — to_entries が適切なパターンです。
入力 (config.json)
{"api_url": "https://api.example.com", "timeout": 30, "retry_count": 3}
# Add cfg_ prefix to every key
jq 'to_entries | map(.key = "cfg_" + .key) | from_entries' config.json
{"cfg_api_url": "https://api.example.com", "cfg_timeout": 30, "cfg_retry_count": 3}
to_entries を {"k": "v"} の中へ [{"key": "k", "value": "v"}]変換します。配列内の項目を変更した後、 from_entries オブジェクトを再構成します。特定のキーだけをリネームしたい場合、他のキーには影響を与えません:
jq 'to_entries | map(if .key == "api_url" then .key = "endpoint" else . end) | from_entries' config.json
レシピ5: を使用してフィールドを削除 del
リシェイプの逆:すべてを保持し、いくつかの特定のフィールドを削除します。主な用途は、ログに記録する前または第三者サービスにパラメータを渡す前に、センシティブデータを削除することです。
jq 'map(del(.email))' users.json
[
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "user"},
{"id": 3, "name": "Carol", "role": "user"}
]
複数のフィールドを一度に削除、またはネストされたパスを削除:
# Multiple top-level fields at once
jq 'del(.password, .token, .refresh_token)' user.json
# Nested path
jq 'del(.user.internal_id)' response.json
# All fields named "debug" at any depth (recursive descent)
jq 'del(.. | .debug? // empty)' response.json
レシピ6: フィルタ、リシェイプ、並べ替えを1回で実行
これらの基本操作を連結する実際の報酬です。ページネートされたAPIレスポンスを扱う際に頻繁に現れるパターンは、ネストされた配列に降り込み、フィルタリングし、オブジェクトをリシェイプし、結果を並べ替えることです。
入力 (repos.json)
{
"total_count": 3,
"items": [
{"id": 1, "name": "repo-alpha", "stargazers_count": 142, "language": "Go", "private": false},
{"id": 2, "name": "repo-beta", "stargazers_count": 89, "language": "Python", "private": true},
{"id": 3, "name": "repo-gamma", "stargazers_count": 310, "language": "Go", "private": false}
]
}
jq '.items
| map(select(.private == false and .language == "Go"))
| sort_by(-.stargazers_count)
| map({name, stars: .stargazers_count})' repos.json
[
{"name": "repo-gamma", "stars": 310},
{"name": "repo-alpha", "stars": 142}
]
パイプライン:
.items— ネストされた配列に降り込むmap(select(...))— 1回のパスでフィルタリング;and条件を連結sort_by(-.stargazers_count)— 降順ソートのため値を否定;sort_by(.field)だけが昇順を提供map({name, stars: .stargazers_count})— 最終的なリシェイプ;{name}は、以下のプロパティのショートハンドです。{name: .name}
レシピ7: ログレベルをカウント group_by
構造化されたログ出力の頻度分析 — データベースは不要、awkの技巧は不要。
入力 (logs.json)
[
{"level": "error", "msg": "connection timeout", "service": "auth"},
{"level": "info", "msg": "request received", "service": "api"},
{"level": "error", "msg": "null pointer exception", "service": "worker"},
{"level": "warn", "msg": "slow query detected", "service": "db"},
{"level": "error", "msg": "rate limit exceeded", "service": "api"}
]
jq 'group_by(.level) | map({level: .[0].level, count: length}) | sort_by(-.count)' logs.json
[
{"level": "error", "count": 3},
{"level": "info", "count": 1},
{"level": "warn", "count": 1}
]
group_by(.level) 配列の配列を返します — 各サブ配列は同じレベル値を持つすべてのエントリを保持します。 .[0].level グループ内の最初のアイテムからレベル名を取得します; length グループ内のエントリの数をカウントします。
同じクエリでサービスごとの分解を追加:
jq 'group_by(.level) | map({
level: .[0].level,
count: length,
services: map(.service) | unique
})' logs.json
速携帯用参考
上記のパターンを表形式にまとめたもの。必要に応じてすぐに確認できるようにします:
| 何をしたいか | jq表現 |
|---|---|
| フィールドのすべての値 | map(.field) |
| 一致するアイテムをフィルタリング | map(select(.field == "val")) |
| 各オブジェクトをリシェイプ | map({newKey: .oldKey}) |
| すべてのキーをリネーム | to_entries | map(.key = ...) | from_entries |
| フィールドを削除 | map(del(.field1, .field2)) |
| 昇順ソート | sort_by(.field) |
| 降順ソート | sort_by(-.field) |
| グループでカウント | group_by(.field) | map({key: .[0].field, count: length}) |
| オブジェクトのすべてのキーを取得 | keys |
| 配列内のアイテム数をカウント | length |
| フィールドのユニークな値 | map(.field) | unique |
開始前に:ブロブをコントロール
APIレスポンスはしばしば最小化または深くネストされています。必要なパスがわからない場合は、JSONを JSONフォーマッタ — カッコブレインで表示され、折りたたみ可能なノードを持つため、必要なパスを確認できます。 jq — 表現を書く前に、変換を実行します。変換後、 JSON比較 ツールは、リシェイプまたはフィールドを削除する際に、意図しない変更がないか確認するために、前後差分を確認するのに役立ちます。
これらの7つのパターンは、コマンドラインで行うJSON処理の大部分をカバーしています。実際の力は、それらを連結することです — フィルタ、リシェイプ、ソートができれば、ほとんどのAPIレスポンスをスクリプトファイルに触らずに処理できます。
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
