Errors and Debugging
jq's errors are better than their reputation — they name the operation, the values involved, and where the input gave out. This chapter collects the ones this guide has been walking toward, reads them closely, and covers the defensive patterns that prevent them. Every error below is real, produced by the command shown.
First, the geography: jq reports on stderr, results on stdout. In a terminal they interleave; in a pipeline only the results flow on. And each failure class has its own exit code (all observed with jq 1.7):
| Failure | Exit |
|---|---|
| Parse error — the input isn't JSON | 5 |
| Compile error — the filter isn't jq | 3 |
| Runtime error — the filter met the wrong data | 5 |
-e with a false/null last result |
1 |
-e with no output at all |
4 |
Parse errors: the input is broken
$ jq '.' broken.json; echo "exit: $?"
jq: parse error: Expected separator between values at line 4, column 37
exit: 5
Line 4, column 37 of broken.json is right after
"nuts" — where the missing comma should be.
Parse errors are positional because they can be:
the parser knows exactly where the JSON stopped
making sense. jq parses the whole value before
any filtering, so a broken input produces no
partial results — nothing came out of that command
but the error.
Compile errors: the filter is broken
$ jq '.models[' models.json; echo "exit: $?"
jq: error: syntax error, unexpected end of file (Unix shell quoting issues?) at <top-level>, line 1:
.models[
jq: 1 compile error
exit: 3
A filter typo is caught before any data flows — jq echoes the filter, points at where it stopped making sense, and exits 3. (The parenthetical about shell quoting is jq being worldly-wise: a filter that arrives truncated by a quoting mishap looks exactly like this.) Compile errors are the friendly kind: no input was harmed, and the fix is in the filter, not the data.
Runtime errors: filter meets data
Cannot iterate over null
Chapter 2 promised this one. Iterate every model's
capabilities — remembering that kestrel-1:1.5b
has none:
$ jq '.models[] | .capabilities[]' models.json; echo "exit: $?"
"completion"
"tools"
"completion"
"completion"
"vision"
jq: error (at models.json:114): Cannot iterate over null (null)
exit: 5
Three lessons in one failure. The five lines
before the error are real output, already gone —
jq streams, so models one through three were fully
processed before the fourth killed the run (the
error reached the terminal on stderr; here it's
shown after the output it interrupted). And the
position, models.json:114, is where the input
value ended — jq read the whole document before
the stream reached the bad element — not where
your filter went wrong. Runtime error positions
locate the data, not the mistake.
The fixes are chapter 2 and 3's tools, chosen by
intent. Skip what's absent, with ?:
$ jq '.models[] | .capabilities[]?' models.json
"completion"
"tools"
"completion"
"completion"
"vision"
"completion"
"tools"
Supply an empty array so the iteration has something legal to chew on:
$ jq '.models[] | (.capabilities // [])[]' models.json
"completion"
"tools"
"completion"
"completion"
"vision"
"completion"
"tools"
Or catch the failure and say so in the output,
with try … catch:
$ jq '.models[] | try (.capabilities[]) catch "none recorded"' models.json
"completion"
"tools"
"completion"
"completion"
"vision"
"none recorded"
"completion"
"tools"
Same seven lines, plus an honest marker where the
gap was. []? when absence is normal, // [] when
the code after it needs an array either way, try
when the failure itself is information.
Wrong types in operations
Arithmetic and indexing check types and refuse mismatches — naming both sides:
$ jq '.models[0].size + .models[0].name' models.json; echo "exit: $?"
jq: error (at models.json:114): number (4378221568) and string ("ember-2:7b") cannot be added
exit: 5
$ jq '.models.name' models.json; echo "exit: $?"
jq: error (at models.json:114): Cannot index array with string "name"
exit: 5
The second is the most common jq error in the
wild: a .name written where .[] needed to come
first. Notice what is not an error, because many
expect it to be: sorting never refuses mixed
types. jq defines a total order —
null < false < true < numbers < strings < arrays <
objects — and sorts anything:
$ jq -n '[3, "ten", 1] | sort'
[
1,
3,
"ten"
]
The "cannot be sorted" error people remember
doesn't exist; its cousins are the cannot be
added family — arithmetic is where types are
actually enforced:
$ jq -n '{"a": 1} + [1]'; echo "exit: $?"
jq: error (at <unknown>): object ({"a":1}) and array ([1]) cannot be added
exit: 5
Output worth diffing: -S, --tab, colour
Three presentation flags earn their place while
debugging. jq preserves input key order, which
makes two runs' outputs differ for no semantic
reason; -S sorts keys for output that diffs
cleanly:
$ echo '{"zeta": 1, "alpha": 2, "mid": 3}' | jq '.'
{
"zeta": 1,
"alpha": 2,
"mid": 3
}
$ echo '{"zeta": 1, "alpha": 2, "mid": 3}' | jq -S '.'
{
"alpha": 2,
"mid": 3,
"zeta": 1
}
Indentation is two spaces by default; --tab
uses tabs, and --indent N picks a width. (The
schedule object below really is indented with
tab characters — your editor will show them as
space, which is itself a small lesson in why diffs
need -S and byte-level honesty.)
$ jq --tab '.schedule' settings.json
{
"hour": 2,
"minute": 30
}
Colour: on a terminal, jq colourises output
(keys, strings, numbers, null each get a shade),
which genuinely helps the eye in a deep object.
-C forces colour on even into a pipe, -M
forces it off; this book is printed in one colour,
so take the claim on trust and try jq -C '.' on
your own terminal.
Numbers: the honest fine print
jq's numbers are double-precision floats, and two behaviours follow. First, an untouched number is printed as written — jq keeps the original text of numbers it doesn't compute with:
$ echo '9007199254740993' | jq '.'
9007199254740993
But arithmetic converts to a double, and doubles cannot name every integer past 2⁵³:
$ echo '9007199254740993' | jq '. + 0'
9007199254740992
Adding zero lost one. For JSON carrying 64-bit
IDs, account numbers, or nanosecond timestamps as
numbers, that is a data-corruption class of
problem: keep such values as strings in the JSON
if they must survive arithmetic, or keep them away
from jq's arithmetic entirely. Second, key order:
as the -S example shows, jq preserves the
document's key order and never promised any
particular one — filters that depend on object
order (rather than on keys) are filters waiting to
surprise you.