Conditionals, defaults and error handling

jq · Advanced · 10 min

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 expr swallows errors, producing no output for that input.
  • try expr catch handler runs the handler with the error message as input: try tonumber catch "bad: \(.)".
  • expr? is shorthand for try 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 5

Remember

  • 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.

Check yourself

  1. What does `{"enabled": false} | .enabled // true` return?
  2. What does `[.[] | .id?]` do with an element that is a string?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: jqlang.org/manual/