Three power features that unlock most “advanced” jq.
—arg / —argjson — pass shell values in safely
Never interpolate a shell variable into the filter string. Bind it instead:
name="Ada"
jq --arg name "$name" '.[] | select(.name == $name)' data.json
--arg always makes a string. For numbers, booleans, or JSON, use --argjson:
jq --argjson min 30 '[.[] | select(.age >= $min)]' data.json
slurp (-s) — read all inputs as one array
-s reads the entire input stream (or several files) into a single array:
jq -s '.' a.json b.json # [ <a>, <b> ]
jq -s 'add' parts/*.json # merge many arrays/objects into one
Turn newline-delimited JSON (NDJSON) into an array:
jq -s '.' events.ndjson
recursive descent (..) — search at any depth
.. streams every value anywhere in the document. Combine with select to find values wherever
they are:
# every "id" value, however deeply nested:
jq '[.. | .id? // empty]' data.json
# every object that has an "error" key, at any depth:
jq '[.. | objects | select(has("error"))]' data.json
paths — where does a value live?
jq 'paths(scalars)' data.json # every path to a leaf value
Bonus: -n with —arg to build JSON from scratch
jq -n --arg u "$USER" --argjson n 5 '{ user: $u, count: $n }'
--arg is for safety, not just convenience — it avoids quoting/injection bugs from building
filter strings in the shell. -s is the answer to “jq only processed the first object” when you
have multiple JSON documents. .. plus objects/scalars/select is how you dig through
unknown-shaped data.