Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Expert jq usage for JSON querying, filtering, transformation, and pipeline integration. Practical patterns for real shell workflows.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-04 | ✗→✓ | ▲ Improved | — | — |
| case-16 | ✗→✓ | ▲ Improved | — | — |
| case-06 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✓ | ▲ Improved | — | — |
jq is the standard CLI tool for querying and reshaping JSON. This skill covers practical, expert-level usage: filtering deeply nested data, transforming structures, aggregating values, and composing jq into shell pipelines. Every example is copy-paste ready for real workflows.
jq inside a bash script or one-linerjq expression doesjq takes a filter expression and applies it to JSON input. Filters compose with pipes (|), and jq handles arrays, objects, strings, numbers, booleans, and null natively.
bash# Extract a field echo '{"name":"alice","age":30}' | jq '.name' # "alice" # Nested access echo '{"user":{"email":"a@b.com"}}' | jq '.user.email' # Array index echo '[10, 20, 30]' | jq '.[1]' # 20 # Array slice echo '[1,2,3,4,5]' | jq '.[2:4]' # [3, 4] # All array elements echo '[{"id":1},{"id":2}]' | jq '.[]'
selectbash# Keep only matching elements echo '[{"role":"admin"},{"role":"user"},{"role":"admin"}]' \ | jq '[.[] | select(.role == "admin")]' # Numeric comparison curl -s https://api.github.com/repos/owner/repo/issues \ | jq '[.[] | select(.comments > 5)]' # Test a field exists and is non-null jq '[.[] | select(.email != null)]' # Combine conditions jq '[.[] | select(.active == true and .score >= 80)]'
bash# Extract a field from every array element echo '[{"name":"alice","age":30},{"name":"bob","age":25}]' \ | jq '[.[] | .name]' # ["alice", "bob"] # Shorthand: map() jq 'map(.name)' # Build a new object per element jq '[.[] | {user: .name, years: .age}]' # Add a computed field jq '[.[] | . + {senior: (.age > 28)}]' # Rename keys jq '[.[] | {username: .name, email_address: .email}]'
bash# Sum all values echo '[1, 2, 3, 4, 5]' | jq 'add' # 15 # Sum a field across objects jq '[.[].price] | add' # Count elements jq 'length' # Max / min jq 'max_by(.score)' jq 'min_by(.created_at)' # reduce: custom accumulator echo '[1,2,3,4,5]' | jq 'reduce .[] as $x (0; . + $x)' # 15 # Group by field jq 'group_by(.department)' # Count per group jq 'group_by(.status) | map({status: .[0].status, count: length})'
bash# String interpolation jq -r '.[] | "\(.name) is \(.age) years old"' # Format as CSV (no header) jq -r '.[] | [.name, .age, .email] | @csv' # Format as TSV jq -r '.[] | [.name, .score] | @tsv' # URL-encode a value jq -r '.query | @uri' # Base64 encode jq -r '.data | @base64'
bash# List all top-level keys jq 'keys' # Check if key exists jq 'has("email")' # Delete a key jq 'del(.password)' # Delete nested keys from every element jq '[.[] | del(.internal_id, .raw_payload)]' # Recursive descent: find all values for a key anywhere in tree jq '.. | .id? // empty' # Get all leaf paths jq '[paths(scalars)]'
bash# if-then-else jq 'if .score >= 90 then "A" elif .score >= 80 then "B" else "C" end' # Alternative operator: use fallback if null or false jq '.nickname // .name' # try-catch: skip errors instead of halting jq '[.[] | try .nested.value catch null]' # Suppress null output with // empty jq '.[] | .optional_field // empty'
bash# Read from file jq '.users' data.json # Compact output (no whitespace) for further piping jq -c '.[]' records.json | while IFS= read -r record; do echo "Processing: $record" done # Pass a shell variable into jq STATUS="active" jq --arg s "$STATUS" '[.[] | select(.status == $s)]' # Pass a number jq --argjson threshold 42 '[.[] | select(.value > $threshold)]' # Slurp multiple JSON lines into an array jq -s '.' records.ndjson # Multiple files: slurp all into one array jq -s 'add' file1.json file2.json # Null-safe pipeline from a command kubectl get pods -o json | jq '.items[] | {name: .metadata.name, status: .status.phase}' # GitHub CLI: extract PR numbers gh pr list --json number,title | jq -r '.[] | "\(.number)\t\(.title)"' # AWS CLI: list running instance IDs aws ec2 describe-instances \ | jq -r '.Reservations[].Instances[] | select(.State.Name=="running") | .InstanceId' # Docker: show container names and images docker inspect $(docker ps -q) | jq -r '.[] | "\(.Name)\t\(.Config.Image)"'
bash# Transpose an object of arrays to an array of objects # Input: {"names":["a","b"],"scores":[10,20]} jq '[.names, .scores] | transpose | map({name: .[0], score: .[1]})' # Flatten one level jq 'flatten(1)' # Unique by field jq 'unique_by(.email)' # Sort, deduplicate and re-index jq '[.[] | .name] | unique | sort' # Walk: apply transformation to every node recursively jq 'walk(if type == "string" then ascii_downcase else . end)' # env: read environment variables inside jq export API_KEY=secret jq -n 'env.API_KEY'
-r (raw output) when passing jq results to shell variables or other commands to strip JSON string quotes--arg / --argjson to inject shell variables safely — never interpolate shell variables directly into filter stringsmap(f) over [.[] | f] for readability-c (compact) for newline-delimited JSON pipelines; omit it for human-readable debuggingjq -n and literal input before embedding in scriptsempty to drop unwanted elements rather than filtering to nulljq is read-only by design — it cannot write files or execute commands--argjq outputs null instead of the expected valueSolution: Check for typos in key names; use keys to inspect actual field names. Remember JSON is case-sensitive.
Solution: Use --argjson instead of --arg when injecting numeric values.
Solution: Ensure the filter string uses single quotes in the shell to prevent variable expansion. Example: jq '.field' not jq ".field".
add returns null on an empty arraySolution: Use add // 0 or add // "" to provide a fallback default.
Solution: Use jq --stream or switch to jstream/gron for very large files.
@bash-pro — Wrapping jq calls in robust shell scripts@bash-linux — General shell pipeline patterns@github-automation — Using jq with GitHub CLI JSON output| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-24 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +25 percentage points is the difference between those two pass rates over the 24 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.