jq recipes: 23 one-liners for JSON on the command line
A runnable collection of jq filters for everyday JSON work: pick fields, filter with select, reshape, sort, count, group, pass shell variables, and export CSV.
jq is a small command-line program that reads JSON, applies a filter, and prints the result. This script runs each recipe against a built-in sample and prints the command next to its output, so you can see exactly what each filter does.
Install jq
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
brew install jq # macOS (Homebrew)
sudo apt install jq # Debian, Ubuntu
winget install jqlang.jq # Windows
The basics in one minute
.is the whole input,.nameis a field,.users[0]is the first array element, and.users[]outputs each element separately.|passes the output of one filter into the next, like a shell pipe.- Wrap a filter in
[ ... ]to collect its outputs into one array. -rprints strings without quotes, which is useful in shell scripts.-cprints each result on one line.- Put the filter in single quotes so the shell does not interpret
$,|or[].
curl -s https://api.github.com/repos/jqlang/jq | jq '.stargazers_count'
jq -r '.users[].email' users.json
The script
Save it as jq-recipes.sh, then run bash jq-recipes.sh. To try a recipe on your own data, copy the filter and run jq '<filter>' yourfile.json.
#!/usr/bin/env bash
set -euo pipefail
command -v jq >/dev/null || { echo "jq is not installed" >&2; exit 1; }
SAMPLE='{
"store": "demo",
"users": [
{"id": 1, "name": "Ada", "email": "[email protected]", "tags": ["admin", "dev"], "active": true, "orders": 3},
{"id": 2, "name": "Brian", "email": null, "tags": ["dev"], "active": false, "orders": 0},
{"id": 3, "name": "Chen", "email": "[email protected]", "tags": [], "active": true, "orders": 5}
]
}'
run() {
local title=$1; shift
local shown="jq" arg
for arg in "$@"; do
if [[ $arg == -* ]]; then shown+=" $arg"; else shown+=" '$arg'"; fi
done
printf '\n### %s\n$ %s\n' "$title" "$shown"
printf '%s\n' "$SAMPLE" | jq "$@"
}
run "Pretty-print" '.'
run "One field" '.store'
run "Raw string, no quotes" -r '.store'
run "Every element of an array" '.users[]'
run "One field from each element" -r '.users[].name'
run "First and last element" '.users[0], .users[-1]'
run "Count elements" '.users | length'
run "Filter with select" '.users[] | select(.active)'
run "Filter by a number" -c '.users[] | select(.orders >= 3) | {name, orders}'
run "Keep results as an array" -c '[.users[] | select(.active) | .name]'
run "Reshape objects" -c '.users | map({id, contact: .email})'
run "Default for null" -r '.users[] | .email // "no email"'
run "Array contains a value" -r '.users[] | select(.tags | index("admin")) | .name'
run "Sort and take top N" -c '.users | sort_by(-.orders) | .[:2] | map(.name)'
run "Sum a field" '[.users[].orders] | add'
run "Group and count" -c '[.users[].tags[]] | group_by(.) | map({tag: .[0], count: length})'
run "List keys" -c '.users[0] | keys'
run "Add or change a field" -c '.users[0] | .vip = true | del(.tags)'
run "Use a shell variable safely" -c --arg who "Chen" '.users[] | select(.name == $who)'
run "Objects to CSV" -r '.users[] | [.id, .name, (.email // "")] | @csv'
run "Objects to TSV" -r '.users[] | [.id, .name] | @tsv'
run "Compact one line per item (JSON Lines)" -c '.users[]'
run "Find every key path" -c '[paths(scalars)] | .[:4]'
Sample output
### Group and count
$ jq -c '[.users[].tags[]] | group_by(.) | map({tag: .[0], count: length})'
[{"tag":"admin","count":1},{"tag":"dev","count":2}]
Notes
select(cond)passes an input through only when the condition is true.map(f)is shorthand for[.[] | f].a // breturnsbwhenaisnullorfalse. It is handy for defaults, but remember thatfalseis replaced too.- Never paste shell variables into the filter string. Pass them with
--arg name value(always a string) or--argjson name 42(parsed as JSON), then use$name. @csvneeds an array of simple values and quotes strings for you.@tsvescapes tabs and line breaks.- jq never edits a file in place. Write to a temporary file and move it:
jq '.version = "2"' app.json > tmp.json && mv tmp.json app.json. Redirecting straight back toapp.jsonempties it before jq reads it. - For JSON Lines input, jq already processes each object in turn. Add
-s(slurp) to read them all into one array first.
See also
- 0057 Shell cheat sheet: everyday commands A quick reference for moving around, managing files, viewing and searching text, checking processes and disk space in bash and zsh, with… Beginner · Reference · Linux, macOS Article Basics · Command-line tools 15 Oct 2026
Repos in the wild
- ripgrep (rg): fast recursive search BurntSushi/ripgrep ripgrep is a line-oriented search tool that recursively searches the current directory for a regex pattern, skipping ignored, hidden and binary files by default. GitHub
- fzf: command-line fuzzy finder junegunn/fzf fzf is an interactive fuzzy finder for the terminal. Pipe any list into it, type a few letters, and pick the item you want. GitHub
- jq: command-line JSON processor jqlang/jq jq is a small command-line tool for reading, filtering and transforming JSON, like sed and awk for structured data. GitHub
- ShellCheck: static analysis for shell scripts koalaman/shellcheck ShellCheck reads your bash or sh scripts and points out bugs, quoting mistakes and portability problems before you run them. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- commitpedia-admin +5,234 chars