jq Tutorial: Query and Transform JSON in the Terminal

Suman SharmaSuman Sharma

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.

Frequently asked questions

What is jq?

jq is a small command-line program for reading, filtering and transforming JSON. It works like sed or awk, but for JSON data.

How do I get a string without quotes in jq?

Use the -r (raw output) flag: jq -r '.name' prints Ada instead of "Ada".

How do I filter an array in jq?

Use select inside map or after iterating: jq '.[] | select(.active == true)' or jq 'map(select(.age > 30))'.

Contact

Get in Touch

Want to call me? book a call and I'll respond in the available time slot. I will ignore all soliciting.