jq takes JSON on standard input, runs a filter, and prints JSON. Once it clicks, you stop opening API responses in an editor.
brew install jq # macOS sudo apt install jq # Debian/Ubuntu winget install jqlang.jq # Windows
All examples below use this file, users.json:
[ { "id": 1, "name": "Ada", "role": "admin", "active": true, "age": 36, "tags": ["ops", "db"] }, { "id": 2, "name": "Linus", "role": "dev", "active": false, "age": 29, "tags": ["kernel"] }, { "id": 3, "name": "Grace", "role": "dev", "active": true, "age": 41, "tags": ["compilers", "db"] } ]
The basics
jq '.' users.json # pretty-print jq -c '.' users.json # compact, one line jq '.[0]' users.json # first element jq '.[-1]' users.json # last element jq '.[0].name' users.json # "Ada" jq -r '.[0].name' users.json # Ada (no quotes) jq 'length' users.json # 3
. is "the current input". .[] iterates an array, emitting each element separately:
jq '.[].name' users.json # "Ada" # "Linus" # "Grace"
Pipes and select
The pipe | passes output from one filter into the next, just like the shell.
# active users only jq '.[] | select(.active)' users.json # names of devs over 30 jq -r '.[] | select(.role == "dev" and .age > 30) | .name' users.json # Grace # users tagged "db" jq '.[] | select(.tags | index("db"))' users.json
map: keep it an array
.[] | ... gives you separate values. Wrap with map() (or [ ... ]) to get an array back:
jq 'map(.name)' users.json # ["Ada","Linus","Grace"] jq 'map(select(.active)) | length' users.json # 2 jq '[.[] | .age] | add / length' users.json # average age
Build new objects
jq 'map({id, name})' users.json jq 'map({user: .name, isAdmin: (.role == "admin")})' users.json
{id, name} is shorthand for {id: .id, name: .name}.
Sort, group, unique
jq 'sort_by(.age) | map(.name)' users.json jq 'sort_by(.age) | reverse | .[0].name' users.json # oldest jq 'group_by(.role) | map({role: .[0].role, count: length})' users.json jq '[.[].tags[]] | unique' users.json
Strings and defaults
jq -r '.[] | "\(.name) is \(.age)"' users.json # string interpolation jq '.[] | .email // "none"' users.json # default when missing or null jq 'map(.name | ascii_downcase)' users.json
Output CSV or TSV
jq -r '.[] | [.id, .name, .role] | @csv' users.json # 1,"Ada","admin" jq -r '.[] | [.id, .name] | @tsv' users.json
Use it with curl
curl -s https://api.github.com/repos/jqlang/jq | jq '{stars: .stargazers_count, open_issues}' # grab a token from a login response into a shell variable TOKEN=$(curl -s -X POST localhost:3000/login -d '{"u":"a","p":"b"}' \ -H 'content-type: application/json' | jq -r '.token')
Pass shell variables in
Don't splice variables into the filter string. Use --arg (string) or --argjson (number, bool, object):
jq --arg role dev '.[] | select(.role == $role) | .name' users.json jq --argjson min 30 'map(select(.age >= $min))' users.json
Edit JSON
jq '.[0].active = false' users.json jq 'map(.age += 1)' users.json jq 'map(del(.tags))' users.json jq '.version = "2.0.0"' package.json > tmp.json && mv tmp.json package.json
jq never edits in place, so write to a temp file and move it.
When jq says "Cannot index array with string"
You used .name on an array. Add .[] or map() first: .[].name, not .name.
If jq says the input itself is invalid, the data isn't JSON. See Fixing "Unexpected token in JSON" or paste it into the JSON Formatter.