A Practical jq Cookbook for Wrangling JSON
A hands-on jq cookbook of copy-paste recipes — pipes, map, select, building objects, group_by, to_entries, and using jq with curl on real API data.
jq is a small, fast command-line tool for slicing, filtering, and reshaping JSON. Once it is in your muscle memory, you stop writing throwaway scripts and start transforming API responses with a single pipeline. This cookbook collects the recipes you will reach for most often, each with a sample input and the output it produces.
The sample data
Most recipes below operate on this users.json file. You can paste the same data into the jq playground to run any recipe without installing anything.
[
{ "id": 1, "name": "Ada", "team": "platform", "active": true, "logins": 42 },
{ "id": 2, "name": "Grace", "team": "platform", "active": false, "logins": 7 },
{ "id": 3, "name": "Linus", "team": "kernel", "active": true, "logins": 99 },
{ "id": 4, "name": "Margaret", "team": "kernel", "active": true, "logins": 12 }
]
The identity filter and pretty-printing
The simplest filter is ., the identity. It returns its input unchanged, which makes it a clean way to pretty-print and color any JSON.
echo '{"name":"Ada","logins":42}' | jq '.'
{
"name": "Ada",
"logins": 42
}
Accessing fields and the pipe
Use .field to reach into an object, and chain steps with the pipe |, exactly like a shell pipeline. Each stage feeds its output to the next.
echo '{"user":{"name":"Ada"}}' | jq '.user.name'
"Ada"
To drop the surrounding quotes and emit raw text, add the -r flag:
echo '{"user":{"name":"Ada"}}' | jq -r '.user.name'
Ada
Iterating an array with .[]
The array iterator .[] explodes an array into a stream of its elements. Each element then flows independently through the rest of the pipeline.
jq '.[].name' users.json
"Ada"
"Grace"
"Linus"
"Margaret"
You can index a single element with .[0] or take a slice with .[1:3].
Transforming with map
While .[] streams elements, map(f) applies a filter to every element and collects the results back into an array. This is the workhorse for reshaping lists.
jq 'map(.name)' users.json
["Ada", "Grace", "Linus", "Margaret"]
map can also build new shapes. Here we keep only the name and login count for each user:
jq 'map({ name, logins })' users.json
[
{ "name": "Ada", "logins": 42 },
{ "name": "Grace", "logins": 7 },
{ "name": "Linus", "logins": 99 },
{ "name": "Margaret", "logins": 12 }
]
Filtering with select
select(condition) passes through only the values for which the condition is true, and drops the rest. Combine it with the array iterator to filter a list. Here we keep only active users (the condition is .active == true):
jq '[ .[] | select(.active == true) ]' users.json
[
{ "id": 1, "name": "Ada", "team": "platform", "active": true, "logins": 42 },
{ "id": 3, "name": "Linus", "team": "kernel", "active": true, "logins": 99 },
{ "id": 4, "name": "Margaret", "team": "kernel", "active": true, "logins": 12 }
]
Numeric filters work the same way. To list the names of users with more than 10 logins, where the condition is .logins > 10:
jq -r '.[] | select(.logins > 10) | .name' users.json
Ada
Linus
Margaret
Building new objects
You construct objects with { } and arrays with [ ]. Reference incoming fields with the dot syntax and rename them freely. This recipe reshapes each user into a compact summary:
jq 'map({ user: .name, busy: (.logins > 20) })' users.json
[
{ "user": "Ada", "busy": true },
{ "user": "Grace", "busy": false },
{ "user": "Linus", "busy": true },
{ "user": "Margaret", "busy": false }
]
Grouping with group_by
group_by(f) sorts the input by a key and then splits it into groups that share that key. The result is an array of arrays. Combine it with map to summarize each group — here we count users per team.
jq 'group_by(.team) | map({ team: .[0].team, count: length })' users.json
[
{ "team": "kernel", "count": 2 },
{ "team": "platform", "count": 2 }
]
To total the logins per team instead, swap length for an add over the mapped logins:
jq 'group_by(.team) | map({ team: .[0].team, total: (map(.logins) | add) })' users.json
[
{ "team": "kernel", "total": 111 },
{ "team": "platform", "total": 49 }
]
Converting objects with to_entries
to_entries turns an object into an array of key/value records, which is perfect for iterating over dynamic keys. Its inverse, from_entries, rebuilds an object. Given a settings object:
echo '{"dark":true,"fontSize":14}' | jq 'to_entries'
[
{ "key": "dark", "value": true },
{ "key": "fontSize", "value": 14 }
]
This pairs naturally with map to remap keys, then from_entries to collapse the result back into an object — a common pattern for renaming or filtering fields by name.
Combining filters
The real power of jq is composition. You can chain select, map, sorting, and slicing into one expression. Here we find active users, sort them by logins descending, and take the top two names:
jq -r '[ .[] | select(.active) ] | sort_by(-.logins) | .[0:2] | .[].name' users.json
Linus
Ada
Using jq with curl
jq shines as the second half of a curl pipeline. Fetch JSON from an API and reshape it on the fly without ever saving a file. This example pulls open issues from a repository and prints just their titles:
curl -s https://api.example.com/repos/acme/app/issues \
| jq -r '.[] | select(.state == "open") | .title'
The -s flag silences curl progress, and -r strips quotes so each title prints as plain text — ready to pipe into grep, sort, or a shell loop.
Conclusion
With a handful of building blocks — the pipe |, the iterator .[], map, select, object construction, group_by, and to_entries — you can answer almost any question about a JSON document in one line. The fastest way to internalize these is to run them against your own data and tweak until the output is exactly what you need.
Open the jq playground, paste the users.json sample from the top of this post, and work through each recipe yourself. Adjust the filters, break things, and watch how the output changes — that hands-on loop is what turns jq from intimidating to indispensable.
Keep reading
Validating JSON with JSON Schema
Learn how JSON Schema works, how to generate a schema from sample data, and how to validate documents with clear, path-based error messages.
JSON vs JSON5 vs JSONC — What's the Difference?
Comments, trailing commas, and unquoted keys — understand JSON5 and JSONC, where each is used, and how to convert them to strict JSON.
How to Format JSON (and Why It Matters)
A practical guide to beautifying, indenting, and minifying JSON — and when to use each, with tips for debugging messy API responses.