jq — Querying JSON from the Terminal: A Practical Guide
If you’ve ever piped curl output to python -m json.tool just to make it readable, you’re already halfway to wanting jq. It’s a lightweight, blazing-fast command-line tool for parsing and transforming JSON — and once it clicks, you’ll reach for it constantly when working with APIs, log files, and config output.
Installing jq
On macOS with Homebrew:
$ brew install jq
On Debian/Ubuntu:
$ sudo apt-get install jq
Verify it’s working:
$ jq --version
jq-1.7.1
Your First Filter
jq reads JSON from stdin and applies a filter. The identity filter . just pretty-prints the input:
$ echo '{"name":"Alice","age":30}' | jq '.'
{
"name": "Alice",
"age": 30
}
To pull out a single field, use .fieldname:
$ echo '{"name":"Alice","age":30}' | jq '.name'
"Alice"
To strip the surrounding quotes from a string, use the -r (raw output) flag:
$ echo '{"name":"Alice","age":30}' | jq -r '.name'
Alice
Working with Arrays
Given a JSON array, .[] iterates over every element:
$ echo '[1,2,3]' | jq '.[]'
1
2
3
To access a specific index, use .[0], .[1], etc.:
$ echo '["a","b","c"]' | jq '.[1]'
"b"
Array slicing works just like Python:
$ echo '[0,1,2,3,4]' | jq '.[2:4]'
[
2,
3
]
Querying Nested Objects
Let’s use a more realistic payload — a GitHub API response for a user:
$ curl -s https://api.github.com/users/torvalds | jq '.'
Drill into nested fields with chained .:
$ curl -s https://api.github.com/users/torvalds | jq '.name, .public_repos, .location'
"Linus Torvalds"
256
"Portland, OR"
Mapping Over Arrays with map
map(f) applies a filter to every element of an array and returns a new array. This is the most useful pattern in jq.
$ echo '[{"name":"Alice","score":90},{"name":"Bob","score":75}]' | jq 'map(.name)'
[
"Alice",
"Bob"
]
Combine with arithmetic:
$ echo '[{"name":"Alice","score":90},{"name":"Bob","score":75}]' | jq 'map({name: .name, grade: (if .score >= 80 then "A" else "B" end)})'
[
{
"name": "Alice",
"grade": "A"
},
{
"name": "Bob",
"grade": "B"
}
]
Filtering with select
select(condition) keeps only elements where the condition is true:
$ echo '[{"name":"Alice","active":true},{"name":"Bob","active":false}]' | jq '.[] | select(.active == true) | .name'
"Alice"
The | in jq is a pipe — it feeds the output of one filter into the next, just like shell pipes.
Filter by numeric comparison:
$ echo '[{"port":80},{"port":443},{"port":8080}]' | jq '[.[] | select(.port > 100)]'
[
{
"port": 443
},
{
"port": 8080
}
]
Building New Objects and Arrays
You can construct a new JSON object from scratch inside a filter using {}:
$ curl -s https://api.github.com/users/torvalds | jq '{login: .login, repos: .public_repos}'
{
"login": "torvalds",
"repos": 256
}
Wrap in [] to collect streamed output back into an array:
$ echo '[{"id":1,"val":10},{"id":2,"val":20}]' | jq '[.[] | {id: .id, doubled: (.val * 2)}]'
[
{
"id": 1,
"doubled": 20
},
{
"id": 2,
"doubled": 40
}
]
Useful Built-in Functions
keys and values
$ echo '{"a":1,"b":2,"c":3}' | jq 'keys'
[
"a",
"b",
"c"
]
length
Works on arrays, objects, and strings:
$ echo '[1,2,3,4,5]' | jq 'length'
5
$ echo '"hello"' | jq 'length'
5
has
Checks if a key exists:
$ echo '{"name":"Alice"}' | jq 'has("age")'
false
to_entries and from_entries
Converts an object to an array of {key, value} pairs and back — useful for iterating over object fields:
$ echo '{"x":1,"y":2}' | jq 'to_entries'
[
{
"key": "x",
"value": 1
},
{
"key": "y",
"value": 2
}
]
group_by and unique_by
$ echo '[{"type":"A"},{"type":"B"},{"type":"A"}]' | jq 'group_by(.type) | map({type: .[0].type, count: length})'
[
{
"type": "A",
"count": 2
},
{
"type": "B",
"count": 1
}
]
Reading from a File
Pass a file directly instead of using stdin:
$ jq '.users | map(.email)' data.json
Use -c (compact) to output one JSON object per line — handy for log processing:
$ jq -c '.[] | select(.level == "error")' app.log.json
{"level":"error","message":"connection refused","ts":1716428800}
{"level":"error","message":"timeout after 30s","ts":1716428900}
Practical One-Liners
Get all running Docker container names:
$ docker inspect $(docker ps -q) | jq -r '.[].Name' | sed 's|/||'
my-api
postgres
redis
Extract AWS instance IDs from describe-instances:
$ aws ec2 describe-instances | jq -r '.Reservations[].Instances[].InstanceId'
i-0abcdef1234567890
i-0fedcba9876543210
Pretty-print and filter a large log file:
$ cat events.json | jq 'select(.severity == "CRITICAL") | {time: .timestamp, msg: .message}'
Conclusion
jq rewards a small upfront investment with enormous payoff — once you know ., map, select, and |, you can handle the vast majority of real-world JSON wrangling without leaving the terminal. The built-in functions like group_by, to_entries, and has fill in the gaps for more complex transformations. Keep the jq manual bookmarked; it’s dense but well-organized and covers everything from string interpolation to recursive descent.