Skip to content
Tools

jq Cheat Sheet: 40 Commands for Filtering and Transforming JSON

·10 min read
jq Cheat Sheet: 40 Commands for Filtering and Transforming JSON - Tools guide from JSON Prettifier

Quick answer: jq is a command-line JSON processor. The five filters that cover most daily work are .key to read a field, .[] to iterate an array, select(condition) to filter, map(filter) to transform every element, and the -r flag to print strings without quotes. Chain them with |: jq -r '.[] | select(.active) | .email' users.json prints the email of every active user, one per line.

You have a 3,000-line API response in the terminal and need the IDs of every record where status is failed. Scrolling is not an option and writing a Python script feels heavy. jq does it in one line, and the forty commands below are the ones that come up again and again. Each row shows the filter, what it does, a tiny input, and the output you will see.

Run a sample expression in the JSONPath Query Tester, or explore the same nested data in the JSON Viewer.

Install jq and Try a Filter

jq is a single binary with no dependencies. Package managers carry it on every platform:

brew install jq            # macOS
sudo apt install jq        # Debian, Ubuntu
winget install jqlang.jq   # Windows
jq --version

The fastest way to learn a filter is to run it against a literal with -n, which starts from null input, so you do not need a file at all:

jq -n '[1, 2, 3] | map(. * 10) | add'
# 60

echo '{"users": [{"name": "Ada"}, {"name": "Bob"}]}' | jq -r '.users[].name'
# Ada
# Bob

How to Read This Cheat Sheet

jq works like a Unix pipeline inside a single program: each filter takes a JSON value in and emits zero or more values out, and | connects them. The command shape is always jq [flags] 'filter' [file], with input from stdin when no file is given. Flags such as -r go before the filter. Rows that start with jq show the full command because the flag is the point; the rest show only the filter.

Basics: Selecting Values

Wrap the filter in single quotes on the shell. Every example assumes the input arrives on stdin or as a file argument.

#FilterWhat it doesInputOutput
1.Identity: outputs the input, pretty-printed{"a":1}{ "a": 1 }
2.nameValue of a key; null if missing{"name":"Ada"}"Ada"
3.user.emailNested path{"user":{"email":"ada@x.io"}}"ada@x.io"
4.items[0]Array index; .items[-1] is the last element{"items":[5,6]}5
5.items[]Iterate: one output per element{"items":[5,6]}5 6
6keysSorted key names (keys_unsorted keeps file order){"b":1,"a":2}["a","b"]
7lengthArray length, string length, or key count[1,2,3]3

Building and Filtering

select keeps values that pass a test; map applies a filter to every element and returns a new array.

#FilterWhat it doesInputOutput
8[.a, .b]Collect several outputs into an array{"a":1,"b":2}[1,2]
9{name, id: .user_id}Build an object; a bare key copies that field{"name":"Ada","user_id":7}{"name":"Ada","id":7}
10.[] | select(.age > 30)Keep only values that pass the test[{"age":25},{"age":40}]{"age":40}
11map(.price * 2)Apply a filter to every element[{"price":2},{"price":5}][4,10]
12map(select(.active))Filter but keep the array wrapper[{"active":true},{"active":false}][{"active":true}]
13del(.password)Remove a key (del(.[0]) removes by index){"id":1,"password":"x"}{"id":1}

Output Flags and Formats

These are command-line flags and @format strings. -r is the one you will reach for most: without it every string output keeps its quotes.

#FilterWhat it doesInputOutput
14jq -r '.name'Raw output: strings without surrounding quotes{"name":"Ada"}Ada
15jq -c '.'Compact: one line per value (JSON Lines output){ "a": [1, 2] }{"a":[1,2]}
16jq -S '.'Sort keys recursively on output{"b":1,"a":2}{ "a": 2, "b": 1 }
17jq -e '.ok'Exit code 1 if the last output is false or null{"ok":false}false (exit status 1)
18jq -r '.[] | @csv'Each inner array becomes a CSV line[["Ada",1]]"Ada",1
19jq -r '.[] | @tsv'Tab-separated, no quoting, tabs escaped[["Ada",1]]Ada<TAB>1

Transforming Objects and Arrays

to_entries and with_entries are the escape hatch whenever you need to treat keys as data.

#FilterWhat it doesInputOutput
20to_entriesObject to array of key/value pairs{"a":1}[{"key":"a","value":1}]
21with_entries(.key |= ascii_upcase)Edit keys or values, then back to an object{"a":1}{"A":1}
22group_by(.t)Array of arrays, grouped and sorted by the field[{"t":"x"},{"t":"y"},{"t":"x"}][[{"t":"x"},{"t":"x"}],[{"t":"y"}]]
23sort_by(.age)Sort by a field; add | reverse for descending[{"age":40},{"age":25}][{"age":25},{"age":40}]
24unique_by(.id)Deduplicate by a field (unique for scalars)[{"id":1},{"id":1},{"id":2}][{"id":1},{"id":2}]
25addSum numbers, concatenate strings, or merge objects[1,2,3]6
26.count |= . + 1Update in place (= assigns, += adds){"count":1}{"count":2}

Strings and Math

jq string functions use regular expressions in the Oniguruma dialect, so most PCRE patterns work.

#FilterWhat it doesInputOutput
27tonumberString to number (tostring goes the other way)"42"42
28split(",")String to array"a,b"["a","b"]
29join("-")Array to string["a","b"]"a-b"
30gsub("a"; "o")Regex replace all (sub replaces the first)"banana""bonono"
31"\(.first) \(.last)"String interpolation{"first":"Ada","last":"L"}"Ada L"

Conditionals and Defaults

The // operator is the fastest way to supply defaults for missing or null fields.

#FilterWhat it doesInputOutput
32if .n > 1 then "many" else "one" endBranching; elif is allowed, end is required{"n":3}"many"
33.nick // .nameAlternative when the left side is null or false{"name":"Ada"}"Ada"
34try tonumber catch "NaN"Catch an error; try alone yields nothing on error"abc""NaN"

Variables and Shell Arguments

--arg always binds a string. Use --argjson when you need a number, boolean, or object.

#FilterWhat it doesInputOutput
35jq --arg n Ada '.[] | select(.name == $n)'Pass a string from the shell as $n[{"name":"Ada"},{"name":"Bob"}]{"name":"Ada"}
36jq --argjson min 30 'map(select(.age >= $min))'Pass a JSON value from the shell[{"age":25},{"age":40}][{"age":40}]
37jq --slurpfile c cfg.json '.env = $c[0].env'Bind a file's values as an array in $c{"a":1} + cfg.json: {"env":"prod"}{"a":1,"env":"prod"}

Input and Output Streams

jq treats a file of concatenated values (including JSON Lines) as a stream and runs the filter once per value.

#FilterWhat it doesInputOutput
38jq -s '.'Slurp every input value into one array{"a":1} {"a":2}[{"a":1},{"a":2}]
39jq -R '.'Raw input: each text line becomes a stringhello"hello"
40jq -c '.[]'Explode an array into one value per line[1,2]1 2

A Few Worth Knowing Beyond the Forty

  • .. walks every value in the tree recursively, so [.. | numbers] collects every number anywhere in the document.
  • has("key") tests for a key even when its value is null, which .key != null cannot distinguish.
  • jq -n '[inputs]' does what -s does but streams, which is the form the jq manual recommends for large files.
  • reduce .[] as $x (0; . + $x) is the general fold when add is not enough.
  • @base64, @uri, @sh, and @json sit alongside @csv and @tsv as output formatters.
  • --tab and --indent 2 control pretty-printing; -j joins raw output without newlines.

Reading JSON From curl

The most common place jq sits is immediately after curl. Use curl's -s so the progress meter does not pollute the stream:

# Pick two fields from one object
curl -s https://api.github.com/repos/jqlang/jq | jq '{name, stars: .stargazers_count}'

# Authenticated request, tab-separated output for a shell loop
curl -s -H "Authorization: Bearer $TOKEN" https://api.example.com/users \
  | jq -r '.[] | [.id, .email] | @tsv'

# Fail loudly if the response is not JSON (an HTML error page, say)
curl -s https://api.example.com/health | jq -e '.status == "ok"' > /dev/null || echo "unhealthy"

When a request needs a JSON body rather than just reading one, the sending JSON with curl guide covers headers, files, and the Windows quoting problems. For a long response where you want to look rather than script, pipe through jq . or paste it into JSON Prettifier, where the collapsible tree view and syntax highlighting make a 3,000-line payload readable.

Patterns You Will Reuse

Combinations that come up in real work, built from the rows above:

# Count records per status
jq 'group_by(.status) | map({status: .[0].status, count: length})' orders.json

# CSV with a header row
jq -r '["id","email"], (.[] | [.id, .email]) | @csv' users.json

# Records missing a required key
jq -c '.[] | select(has("email") | not)' users.json

# Rename a key everywhere
jq 'map(with_entries(if .key == "userId" then .key = "user_id" else . end))' events.json

# Convert an object of objects into an array with the key inside
jq 'to_entries | map(.value + {id: .key})' lookup.json

# Deep-merge a defaults file into each record
jq --slurpfile d defaults.json 'map($d[0] * .)' records.json

The last one uses * on objects, which merges recursively, with the right-hand side winning on conflicts. For converting between JSON arrays and one-record-per-line files, the JSON Lines guide has a dedicated table of jq commands, and the JSON to CSV guide goes deeper on @csv with nested data.

Gotchas

  • Forgetting -r. NAME=$(jq '.name' f.json) stores "Ada" with the quotes. Add -r whenever the result goes to the shell.
  • Cannot index array with "name". The input is an array and the filter expects an object. Put .[] or map(...) in front.
  • Cannot iterate over null. .items[] ran on a record with no items key. Write .items[]? to skip silently or (.items // [])[] to default to an empty array.
  • --arg is always a string. select(.age > $min) with --arg min 30 compares a number to a string and matches nothing. Use --argjson.
  • Windows quoting. cmd.exe does not treat single quotes as quotes. Use double quotes and escape inner ones: jq ".[] | select(.name == \"Ada\")", or put the filter in a file and use -f filter.jq. In PowerShell, single quotes work but call jq.exe explicitly.
  • Invalid input. jq stops at the first parse error with a line number. If the file came from a hand-edited config, a trailing comma is the usual culprit; the common JSON errors guide lists the rest.
  • Keys with dashes or spaces. .user-id is parsed as subtraction. Write .["user-id"].

Frequently Asked Questions

What is jq used for?
jq is a command-line tool for reading, filtering, and transforming JSON. It is most often used to pull specific fields out of API responses, convert JSON to CSV or plain text for scripts, and reshape data between formats without writing a program.
How do I get jq output without quotes?
Add the -r (raw output) flag: jq -r '.name' prints Ada instead of "Ada". Without -r, every string result is printed as a JSON string literal, which is correct for piping into another JSON tool but wrong for shell variables.
What does 'Cannot index array with string' mean in jq?
You applied an object filter such as .name to an array. Iterate first with .[] so the filter runs on each element: .[] | .name, or use map(.name) to keep the results in an array.
How do I pass a shell variable into jq?
Use --arg for strings and --argjson for numbers, booleans, or JSON objects: jq --arg name "$NAME" '.[] | select(.name == $name)'. Never splice the variable into the filter string with shell interpolation; it breaks on quotes and allows injection.
How do I use jq with curl?
Pipe the response into jq: curl -s https://api.example.com/users | jq '.[0]'. The -s flag on curl hides the progress meter so only the body reaches jq. Add -r on jq when you want plain strings for the shell.
Why does jq say 'syntax error, unexpected INVALID_CHARACTER' on Windows?
cmd.exe does not understand single quotes, so the filter is split and mangled. On cmd.exe wrap the filter in double quotes and escape inner double quotes with a backslash, or save the filter in a file and run jq -f filter.jq. In PowerShell single quotes work, but call jq.exe explicitly.

Need to Look Before You Filter?

Paste the raw API response, format it, and sort the keys to see what fields you are working with. Then write the jq filter with confidence.

Format My JSON