Real JSON nests. Here’s how to filter on a value that lives a level (or two) down, or inside an inner array.
Example input
[
{ "name": "Ada", "address": { "city": "London" }, "tags": ["dev","math"] },
{ "name": "Linus", "address": { "city": "Portland" }, "tags": ["kernel"] },
{ "name": "Mae", "address": { "city": "London" }, "tags": ["dev","space"] }
]
Filter by a nested object field
jq '[.[] | select(.address.city == "London")]' data.json
Output:
[
{ "name": "Ada", "address": { "city": "London" }, "tags": ["dev","math"] },
{ "name": "Mae", "address": { "city": "London" }, "tags": ["dev","space"] }
]
Filter where an inner array CONTAINS a value
jq '[.[] | select(.tags | index("dev"))]' data.json
index("dev") returns the position (truthy) if present, null if not — so select keeps the
matches. IN / any work too:
jq '[.[] | select(.tags | any(. == "dev"))]' data.json
Filter where ANY nested item matches a condition
{ "orders": [ { "items": [ {"sku":"A","qty":5}, {"sku":"B","qty":1} ] } ] }
jq '.orders[] | select(.items | any(.qty > 3))' nested.json
Safe when the nested key may be missing
Use ? so absent paths don’t error:
jq '[.[] | select(.address.city? == "London")]' data.json
any(condition) is the workhorse for “at least one sub-item matches”; all(condition) for
“every sub-item matches”. Both take the array on their left via a pipe: .tags | any(. == "dev").