Real-world JSON is messy: missing fields, nulls and unexpected types need graceful handling.
if expressions always need an else before jq 1.7 (since 1.7 it may be omitted and defaults to .): if .ms > 1000 then "slow" elif .ms > 200 then "ok" else "fast" end. Like everything in jq, if is an expression producing a value, so it can appear inside object construction.
The alternative operator a // b outputs a unless it produces only false or null (or nothing), in which case it outputs b. It is the idiomatic default: .name // "unknown", .tags // []. Careful: false // true is true, so it is not suitable for defaulting booleans that may legitimately be false; use if .flag == null then true else .flag end there.
Errors: indexing the wrong type (.foo on a string), tonumber on "abc" and explicit error("msg") raise errors that stop jq with exit code 5.
try exprswallows errors, producing no output for that input.try expr catch handlerruns the handler with the error message as input:try tonumber catch "bad: \(.)".expr?is shorthand fortry expr:.[] | .id?skips elements that are not objects.
Combine them to normalise sloppy data: (.port | tonumber? ) // 80 converts a port string or falls back to 80.
Example
echo '[{"ms":1500},{"ms":300},{"ms":20},{"name":"x"}]' > t.json
jq -c 'map(if .ms == null then "n/a" elif .ms > 1000 then "slow" elif .ms > 200 then "ok" else "fast" end)' t.json
# ["slow","ok","fast","n/a"]
jq -c 'map(.name // "unknown")' t.json # ["unknown","unknown","unknown","x"]
echo '["8080","abc",null]' | jq -c 'map((tonumber? // 80))' # [8080,80,80]
echo '"abc"' | jq 'try tonumber catch "bad input"' # "bad input"
echo '[1,{"id":2},"x"]' | jq -c '[.[] | .id?]' # [2]
echo 'false' | jq '. // true' # true (beware!)
echo '{}' | jq 'error("missing id")'; echo "exit $?" # jq: error ... exit 5Remember
- if/elif/else/end is an expression
- a // b defaults when a is null, false or empty
- try/catch and ? handle errors per value
Watch out for
- Using // to default booleans: a real false value is replaced by the default.