jq Cheat Sheet: Filters You Will Actually Use
Most jq tutorials teach you the language from the ground up. That's useful once, but when you're staring at a 4,000-line API response in the terminal you want the filter, not the theory. This jq cheat sheet is the set of filters that cover almost everything you'll do day to day: pulling fields, filtering arrays, reshaping objects, and turning JSON into CSV. Every example below was run against jq 1.7.1, and the output shown is the real output.
All the examples use this file, orders.json:
If you'd rather look at the structure before writing a filter, paste it into the JSON viewer first. Knowing the shape saves you half the trial and error.
Getting values out
| Filter | Output |
|---|---|
.store | "north" |
.orders[0].customer.name | "Ada" |
.orders[-1].id | 103 |
.orders[].id | 101, 102, 103 (three separate outputs) |
The .orders[].id row trips people up. .orders[] doesn't return an array, it returns a stream of values, one per element. If you want an array back, wrap the expression in brackets: [.orders[].id] gives [101,102,103].
Add -r when you want raw strings without the quotes, which is what you want any time the output feeds another shell command:
Filtering with select
select keeps a value when the condition is true and drops it otherwise. It's the filter you'll type most.
For regex matching use test, which takes flags as a second argument:
map(f) is shorthand for [.[] | f], so map(select(...)) filters an array and keeps it an array:
Reshaping objects
You rarely want the whole object. Build a new one with only the fields you need. {id, total} is shorthand for {id: .id, total: .total}:
For nested fields, jq 1.7 added pick, which keeps the path structure intact:
If you're on jq 1.6 (still the default on older Ubuntu and Debian images), pick doesn't exist and you'll get jq: error: pick/1 is not defined. Check with jq --version.
The opposite of pick is del:
To turn an array into a lookup object keyed by id, use a computed key in parentheses and add to merge:
The tostring matters. Object keys in JSON are always strings, and jq will refuse a number as a key.
Defaults, sums and grouping
The alternative operator // returns the right side when the left is null or false:
Aggregates work on arrays, so collect first, then reduce:
group_by sorts by the key as a side effect, so don't rely on the original order surviving it.
Updating values in place
|= applies a filter to a value and writes the result back, leaving everything else in the document untouched:
Here's a gotcha that costs people real time. The pipe has the lowest precedence of anything in jq, so this does not do what it looks like:
jq reads that as (.total |= . * 100) | round, so round runs on the whole order object. Wrap the right-hand side in parentheses:
When an update filter errors with "number required" or "cannot be parsed as a number", missing parentheses is the first thing to check.
Passing shell variables
Never splice shell variables into the filter string with double quotes. It breaks on any value with a quote in it and it's an injection risk if the value comes from user input. Use --arg for strings and --argjson for numbers, booleans or JSON:
A common bug: --arg min 40 passes the string "40", and in jq every string sorts after every number, so .total >= $min is false for all orders. If you're comparing numbers, use --argjson.
Output formats: CSV, TSV and strings
@csv and @tsv take an array per row:
@csv only accepts flat values. A nested object or array in the row gives you is not valid in a csv row. For anything with nesting, flatten it first or use the JSON to CSV converter, which flattens nested keys into dotted column names for you.
String interpolation uses \(...) inside a string:
Flags worth memorizing
| Flag | What it does |
|---|---|
-r | Raw output, no quotes around strings |
-c | Compact output, one JSON value per line |
-s | Slurp: read all inputs into one array (useful for JSON Lines) |
-n | Don't read input, start from null |
-e | Exit status 1 if the last output is false or null, 4 if there was no output |
--tab / --indent 4 | Change the pretty-print indentation |
--arg / --argjson | Pass variables in safely |
-e is what makes jq usable in scripts and CI. jq -e '.orders[] | select(.id == 999)' orders.json prints nothing and exits 4, so an if in bash can branch on it. For more on -s with line-delimited files, see the post on JSON Lines and NDJSON.
jq vs JSONPath
If you've used JSONPath in Postman, Kubernetes or a test framework, most jq filters have a direct equivalent:
The difference is that JSONPath only selects. jq also transforms, sums, groups and writes values back. If all you need is to find a value, JSONPath is shorter, and you can test expressions against your own data in the JSONPath tester without installing anything.
Two errors you'll see
Cannot index array with string "id" means you used .orders.id on an array. Add []: .orders[].id.
Cannot index string with string "name" means the left side is a string, not an object. Here .store.name fails because .store is "north". Note that indexing null doesn't error, it just returns null, so .orders[0].missing.deeper quietly gives you null and a typo in a field name can go unnoticed. Add -e when you need missing fields to fail loudly.
When the input itself is broken, jq stops with parse error and a line and column. Run the file through the JSON formatter to see exactly where the bad comma or quote is, then come back to jq.