Skip to content
Back to Blog
By JSONConvert Team··8 min read

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

FilterOutput
.store"north"
.orders[0].customer.name"Ada"
.orders[-1].id103
.orders[].id101, 102, 103 (three separate outputs)
Pipes chain filters, the same way they do in the shell:

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

FlagWhat it does
-rRaw output, no quotes around strings
-cCompact output, one JSON value per line
-sSlurp: read all inputs into one array (useful for JSON Lines)
-nDon't read input, start from null
-eExit status 1 if the last output is false or null, 4 if there was no output
--tab / --indent 4Change the pretty-print indentation
--arg / --argjsonPass 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.

Related Tools