Extract, Reshape and Validate JSON with jq
A 2,000-line JSON blob and you need one field: jq gets it in a single line. Learn to select records, reshape them and make a script stop when the data is missing. Allow about fifteen minutes.
The route
Jump straight to the step you need, or tick off Done means at the end.
- You need: a shell,
jq, and a JSON file or a command that produces JSON. - Read-only: nothing here changes a file unless you deliberately redirect output over an existing one.
- Tested on: jq 1.7.1, the version installed on this machine. Its manpage is the local contract for these examples.
1. Check the installed version
Check the executable before relying on an option:
$ jq --version
jq-1.7
Yes, that says 1.7. The package is 1.7.1-3ubuntu0.24.04.2, but the program reports jq-1.7. Keep that distinction in bug reports and deployment notes.
2. Pretty-print and inspect JSON
Start with the identity filter: a single full stop. jq reads a stream of JSON values and pretty-prints each result by default.
$ printf '%s\n' '{"user":{"name":"Ada","roles":["admin","reviewer"]}}' | jq '.'
{
"user": {
"name": "Ada",
"roles": [
"admin",
"reviewer"
]
}
}
Tip
Always wrap the filter in single quotes so the shell leaves jq's punctuation alone.
Checkpoint
Formatted JSON means the executable and input parsing both work. A parse error means you should inspect the producer first; do not hide malformed input behind a cleverer filter.
3. Select fields and array items
- Dot notation for identifier-like keys:
.name. - Bracket notation for keys containing punctuation.
- Array indexes start at zero.
.[]iterates, emitting one result per item.
$ printf '%s\n' '[{"name":"Ada","active":true},{"name":"Grace","active":false}]' \
| jq '.[] | select(.active) | .name'
"Ada"
select keeps its input only when the condition is true. The pipe feeds each result on the left into the filter on the right.
For a plain shell string instead of a quoted JSON string, add -r:
$ printf '%s\n' '{"name":"Ada"}' | jq -r '.name'
Ada
Tip
Raw output is a presentation choice, not validation. Keep JSON output when the next command also expects JSON.
4. Reshape records into a compact result
Object construction gives you a stable output shape. This builds a new object for each active record, and -c puts the result on one line:
$ printf '%s\n' '[{"name":"Ada","role":"admin","active":true},{"name":"Grace","role":"reviewer","active":false}]' \
| jq -c '[.[] | select(.active) | {name, role}]'
[{"name":"Ada","role":"admin"}]
Warning
The outer square brackets collect all results into one JSON array. Without them jq emits separate values, one per match, and a downstream program expecting one document may receive several valid documents instead. This is a common pipeline trap.
For a key whose spelling is not a jq identifier, quote it:
$ printf '%s\n' '{"user-name":"Ada"}' | jq -r '."user-name"'
Ada
5. Pass values in, do not paste them in
Keep data out of the filter text and you avoid fragile shell interpolation:
--argbinds a string.--argjsonbinds a JSON value.
$ printf '%s\n' '[{"name":"Ada","active":true},{"name":"Grace","active":false}]' \
| jq --arg wanted 'Ada' '[.[] | select(.name == $wanted)]'
[
{
"name": "Ada",
"active": true
}
]
$ jq -n --argjson limit 3 '$limit * 2'
6
Warning
--arg wanted 123 binds the string "123", not the number 123. Use the option that matches the type your filter expects.
6. Validate input and make scripts stop
Parsing with the identity filter is a handy read-only check:
$ jq -e '.' data.json >/dev/null
$ echo $?
0
With -e, the exit status tells you about the last result:
- 0: the last result was neither
falsenornull. - 1: the last result was
falseornull. - 4: no valid result was produced.
- Anything else: a usage, system or compilation failure.
That makes an explicit predicate useful in a script:
if jq -e 'any(.[]; .active == true)' data.json >/dev/null; then
printf '%s\n' 'at least one active record'
else
status=$?
printf 'no active record or jq failed (status %s)\n' "$status" >&2
exit "$status"
fi
Warning
Not every failure means nothing matched. A status such as 2 or 3 means the command or filter failed, so read stderr and check the filter before you touch the data.
7. Handle large inputs deliberately
jq normally parses one JSON value at a time. Two options change that:
-sslurps all input values into one big array. Handy for aggregation, but memory use grows with the input.--streamemits path and leaf-value arrays from a large single document, so a filter can reduce it incrementally. It changes the input shape; it is not a drop-in speed switch for an existing filter.
Two formatting options for reviews and logs (they change formatting, not the selected values):
-Ssorts object keys, for stable diffs and tests.-Mturns off terminal colour, for logs and snapshots.
8. Write output safely
Everything above writes to standard output only. When you need a file, redirect to a new name first and check it:
$ jq '.' data.json > data.pretty.json
$ jq -e '.' data.pretty.json >/dev/null
Checkpoint
The second command should exit 0. If the output is wrong, remove it with rm -- data.pretty.json or keep it for comparison; the original data.json is untouched.
Warning
Never write jq '.' data.json > data.json. The shell truncates the destination before jq can finish reading it, and there is no undo unless another copy exists.
Warning
Use sudo only when the input or destination genuinely needs elevated access; filtering a file in your working directory normally needs none. Do not give jq extra access just to silence a permissions error. Pick a permitted working directory or fix ownership through your normal administration process.
Done means
- Version confirmed:
jq --versionreports the expected installed release. - Errors told apart: you can format input with
jq '.'and distinguish malformed JSON from a filter error. - Records reshaped: you can select records, build a new object and choose between JSON and raw string output.
- Types right:
--argsupplies strings,--argjsonsupplies JSON values. - Scripts stop: your validation uses
-eon purpose and checks its exit status. - Originals safe: any transformed file went to a new path and was checked before it replaced anything.