jq is the command-line tool I reach for when a JSON response is too large to read, too nested for grep, or too sensitive to paste into a random web page. It parses JSON properly, applies a filter, and writes the result to standard output.
The shortest useful commands are:
jq . data.json # pretty-print and validate
jq -c . data.json # compact JSON
jq -r '.data.token' resp.json # raw string output, no quotes
jq '.items[] | .id' resp.json # one id per array item
jq empty data.json # validate only, no output
This tutorial uses realistic API-shaped examples instead of isolated tricks. By the end, you should be able to inspect a response, pull the fields you need, filter rows, reshape objects, pass shell variables safely, validate files in CI, and avoid the common jq errors that make beginners think the tool is stranger than it really is.
What jq Does
jq reads JSON from a file or standard input, runs a filter, and prints JSON again unless you ask for raw text.
Think of a jq filter as a tiny pipeline:
input JSON -> jq filter -> output JSON or text
The identity filter, ., means "return the current value unchanged." That is why jq . file.json is both a formatter and a syntax validator.
jq is best when the input is already valid JSON. If the payload has trailing commas, single quotes, comments, or half-copied text, run it through JSON Validator or JSON Fix first. jq is excellent at querying valid JSON; it is not a repair tool.
Install jq
# macOS
brew install jq
# Debian / Ubuntu
sudo apt-get install jq
# Windows
winget install jqlang.jq
# Verify the binary that your shell will run
jq --version
Most examples in this guide use single quotes around the jq filter. That is the least surprising option in macOS, Linux, zsh, bash, and PowerShell:
jq '.orders[] | .id' orders.json
If you are using Windows cmd.exe, quote handling is different. Use double quotes around the filter and escape inner double quotes, or run the command from PowerShell, Git Bash, WSL, or another shell with normal single-quote behavior.
Use One Sample File
Save this as orders.json if you want to run the examples locally:
{
"orders": [
{
"id": "ord_1001",
"status": "paid",
"total": 42.5,
"customer": { "id": 7, "name": "Ada", "tier": "pro" },
"items": [
{ "sku": "json-pro", "qty": 1, "price": 29.5 },
{ "sku": "support", "qty": 1, "price": 13 }
],
"tags": ["api", "renewal"]
},
{
"id": "ord_1002",
"status": "failed",
"total": 0,
"customer": { "id": 8, "name": "Lin", "tier": "free" },
"items": [],
"tags": ["trial"]
},
{
"id": "ord_1003",
"status": "paid",
"total": 118,
"customer": { "id": 9, "name": "Grace", "tier": "enterprise" },
"items": [
{ "sku": "json-pro", "qty": 4, "price": 29.5 }
],
"tags": ["api", "bulk"]
}
],
"nextCursor": null
}
Using the same file makes the filters easier to read. You can see when the current value is the whole document, one order, one item, or one string.
Pretty-Print, Minify, and Sort Keys
The identity filter returns the input:
jq . orders.json
That pretty-prints the file and validates it at the same time. If the JSON is malformed, jq prints a parse error and exits non-zero.
For compact output:
jq -c . orders.json
For stable diffs, sort object keys:
jq -S . orders.json
-S is useful before committing generated JSON, comparing API fixtures, or reviewing a config file where key order changes create noisy diffs. It does not sort arrays. If array order matters, sort those explicitly with sort_by.
Read Object Fields and Array Items
Use .key for object fields:
jq '.nextCursor' orders.json
# null
Use nested paths for nested objects:
jq '.orders[0].customer.name' orders.json
# "Ada"
Use .[] to iterate an array:
jq '.orders[]' orders.json
That emits three separate JSON values, one for each order. Once you are inside each order, select a field from the current value:
jq '.orders[] | .id' orders.json
# "ord_1001"
# "ord_1002"
# "ord_1003"
When you want plain strings for a shell script, add -r:
jq -r '.orders[] | .id' orders.json
# ord_1001
# ord_1002
# ord_1003
That difference matters. JSON strings include quotes; raw output does not.
Handle Missing Fields Without Breaking the Pipeline
If a field may be missing, use ? and //.
jq -r '.orders[] | .coupon? // "no coupon"' orders.json
The ? keeps jq from failing when the field does not exist. The // operator provides a fallback when the value is null or missing.
For optional arrays, default to an empty array before iterating:
jq '.discounts // [] | .[]' order.json
This pattern avoids the common Cannot iterate over null error.
Filter Arrays with select
Use select() when you want to keep only values that match a condition.
Paid orders:
jq '.orders[] | select(.status == "paid")' orders.json
Paid orders over 100:
jq '.orders[] | select(.status == "paid" and .total > 100)' orders.json
Orders tagged api:
jq '.orders[] | select(.tags | index("api"))' orders.json
Customers whose tier contains pro, case-insensitive:
jq '.orders[] | select(.customer.tier | test("pro"; "i"))' orders.json
For boolean fields, you do not need == true:
jq '.users[] | select(.active)' users.json
Use the explicit form only when it makes the intent clearer to the team reading the script later.
Build a New JSON Shape
Object construction is where jq becomes more than a viewer. You can turn a noisy API response into the small shape your script needs:
jq '.orders[] | {
orderId: .id,
customer: .customer.name,
tier: .customer.tier,
amount: .total
}' orders.json
That emits one object per order. To keep the result as a JSON array, wrap the iteration in brackets:
jq '[.orders[] | {
orderId: .id,
customer: .customer.name,
tier: .customer.tier,
amount: .total
}]' orders.json
If you already have an array as the current value, map() is often clearer:
jq '.orders | map({
orderId: .id,
customer: .customer.name,
amount: .total
})' orders.json
Use the bracket form when you are streaming values from several places. Use map() when you are transforming one array into another array.
Count, Sum, Sort, and Group
length counts arrays, object keys, and string characters:
jq '.orders | length' orders.json
# 3
Sum order totals:
jq '[.orders[].total] | add // 0' orders.json
# 160.5
The // 0 fallback matters because add returns null for an empty array.
Sort orders by total:
jq '.orders | sort_by(.total)' orders.json
Sort descending:
jq '.orders | sort_by(.total) | reverse' orders.json
Group by status:
jq '.orders
| sort_by(.status)
| group_by(.status)
| map({ status: .[0].status, count: length })' orders.json
group_by() expects sorted input for predictable results, so sort on the same key first.
Export JSON to CSV or TSV
jq can emit CSV rows with @csv:
jq -r '.orders[]
| [.id, .customer.name, .status, .total]
| @csv' orders.json
Output:
"ord_1001","Ada","paid",42.5
"ord_1002","Lin","failed",0
"ord_1003","Grace","paid",118
Add your own header row:
jq -r '(["id","customer","status","total"] | @csv),
(.orders[] | [.id, .customer.name, .status, .total] | @csv)' orders.json
For logs and shell pipelines, TSV is often easier to read:
jq -r '.orders[] | [.id, .status, .total] | @tsv' orders.json
If you need nested flattening, stable headers, or spreadsheet-friendly type handling, use the full JSON to CSV guide. jq is great for quick exports, but production CSV conversion usually needs clearer rules.
Pass Shell Variables Safely
Do not splice shell variables into the jq filter string. Pass them with --arg for strings and --argjson for JSON values.
status="paid"
jq --arg status "$status" '.orders[] | select(.status == $status)' orders.json
For a numeric threshold:
min_total=50
jq --argjson min "$min_total" '.orders[] | select(.total >= $min)' orders.json
For a JSON array:
allowed='["pro","enterprise"]'
jq --argjson tiers "$allowed" \
'.orders[] | select(.customer.tier as $tier | $tiers | index($tier))' \
orders.json
--arg always creates a string. --argjson parses the value as JSON. Use --argjson only when you control the value or have already validated it.
Update and Redact JSON
jq writes to standard output. It does not edit files in place.
Set a field:
jq '.settings.debug = false' config.json
Update a field from its current value:
jq '.count |= . + 1' counter.json
Delete sensitive fields before sharing a sample:
jq 'del(.password, .secret, .token)' record.json
Redact nested keys in every order:
jq '.orders |= map(del(.customer.id))' orders.json
To save a change back to the same file, write to a temporary file and move it only if jq succeeds:
tmp=$(mktemp)
jq '.settings.debug = false' config.json > "$tmp" && mv "$tmp" config.json
That pattern prevents an invalid command or parse error from replacing your file with an empty file.
Search Deeply with Recursive Descent
Recursive descent, .., walks every value in the JSON tree. Combine it with optional field access when you need to find a key no matter where it appears:
jq '[.. | .id? // empty]' data.json
That is handy for unfamiliar payloads, but it is not a substitute for understanding the structure. On large files, recursive descent can be noisy and slow. Use it to discover the shape, then replace it with a specific path once you know where the data lives.
Validate JSON with jq
Because jq must parse the input before filtering it, this command is a clean syntax check:
jq empty data.json
It prints nothing when the file is valid. It exits non-zero when the file is invalid, which makes it useful in CI:
find . -name '*.json' -not -path './node_modules/*' -print0 |
while IFS= read -r -d '' file; do
jq empty "$file" || exit 1
done
Use How to Validate JSON if you need the broader split between syntax validation, JSON Schema validation, and application-level checks.
Use jq with curl
jq pairs naturally with API calls:
curl -sS https://api.example.com/orders |
jq -r '.orders[] | select(.status == "failed") | [.id, .customer.name] | @tsv'
For real scripts, keep the HTTP checks outside jq. A 500 error page is not JSON, and jq will only tell you that parsing failed. Save or inspect the raw response when a pipeline unexpectedly starts with a parse error:
curl -fsS https://api.example.com/orders -o response.json
jq '.orders | length' response.json
If the API can return non-JSON error bodies, check status and headers before piping directly into jq.
Common jq Errors and What They Mean
| Error | What usually happened | Fix |
|---|---|---|
parse error: Invalid numeric literal |
The input is not valid JSON, often HTML or a truncated response. | Inspect the raw file with head or validate it with JSON Validator. |
Cannot index array with string "name" |
Your filter expects an object, but the current value is an array. | Add .[] or index the array first, such as `.users[] |
Cannot iterate over null |
You used .[] on a missing or null value. |
Use `.items // [] |
Cannot index string with string "id" |
The current value is already a string. | Move the field access earlier in the pipeline. |
| Output still has quotes | jq is printing JSON strings. | Add -r for raw text output. |
| Shell says the command is malformed | The shell ate part of your jq filter. | Wrap the filter in single quotes, and pass values with --arg or --argjson. |
When a jq command surprises you, add the simplest possible filter at each step:
jq '.orders' orders.json
jq '.orders[]' orders.json
jq '.orders[] | .customer' orders.json
jq '.orders[] | .customer.name' orders.json
That small-step debugging style beats staring at a 90-character one-liner.
Copy-Paste jq Cheat Sheet
| Task | Command |
|---|---|
| Pretty-print JSON | jq . file.json |
| Minify JSON | jq -c . file.json |
| Sort object keys | jq -S . file.json |
| Validate JSON syntax | jq empty file.json |
| Read one field | jq '.data.token' response.json |
| Read raw string | jq -r '.data.token' response.json |
| First array item | jq '.[0]' list.json |
| All array items | jq '.[]' list.json |
| One field from each item | `jq -r '.items[] |
| Filter array rows | `jq '.items[] |
| Filter with fallback | `jq '.items[] |
| Count array items | `jq '.items |
| Sum a field | `jq '[.items[].amount] |
| Sort by field | `jq '.items |
| Build new objects | `jq '.items[] |
| Delete a field | jq 'del(.token)' response.json |
| Export CSV rows | `jq -r '.items[] |
| Pass a string variable | `jq --arg id "$id" '.items[] |
| Pass a numeric variable | `jq --argjson min 10 '.items[] |
When a Browser Tool Is Faster
jq is the right tool for scripts, CI, repeatable API checks, and large local files. A browser tool is faster when you are doing a one-off visual inspection:
- JSON Viewer for expanding and collapsing a nested response.
- JSON Validator for checking syntax with line and column feedback.
- JSON Fix for repairing common mistakes before jq can parse the file.
- How to Format JSON when you need formatting options in JavaScript, Python, jq, or the browser.
- How to Compare Two JSON Files when stable sorting and diff-friendly output matter.
Frequently Asked Questions
How do I pretty-print JSON with jq?
Run jq . file.json. The identity filter . returns the input, and jq formats valid JSON by default. Use jq -c . file.json when you want compact one-line JSON instead.
How do I extract a field with jq?
Use a path filter such as jq '.data.token' response.json. For arrays, iterate first: jq -r '.items[] | .id' response.json.
How do I filter a JSON array with jq?
Iterate with .[] and keep matching rows with select(), for example jq '.orders[] | select(.status == "paid" and .total > 100)' orders.json.
Why does jq output strings with quotes?
jq prints JSON by default, and JSON strings include quotes. Add -r when you want raw text for shell variables, filenames, IDs, CSV pipelines, or clipboard output.
Can jq update a JSON file in place?
No. jq writes the modified JSON to standard output. Save through a temporary file, for example tmp=$(mktemp); jq '.debug = false' config.json > "$tmp" && mv "$tmp" config.json.
How do I pass a shell variable to jq?
Use --arg name "$value" for strings and --argjson name "$json" for numbers, booleans, arrays, or objects. Reference the value inside the filter as $name.
Can jq validate JSON?
Yes. jq empty file.json parses the input, prints nothing when it is valid, and exits non-zero when it is invalid. It validates syntax, not JSON Schema rules.
Why do I get Cannot iterate over null in jq?
You used .[] on a missing or null value. Default the value first with .items // [] | .[], or use optional iteration with .items[]? when missing arrays are acceptable.
Keep Going
Sources
- jq manual for filters, operators, string interpolation, and formatters
- jq download for installation options
- DigitalOcean: How To Transform JSON Data with jq
Last reviewed July 2026.