Schemaless or deeply nested JSON, like Kubernetes manifests or Terraform state, needs tools that work at any depth.
.. (recurse) outputs the input and every value nested inside it, depth-first. It answers "find X anywhere": .. | .image? // empty lists every image field no matter how deep, and .. | numbers every number. (? and // empty skip non-objects and nulls.)
A path is an array of keys and indexes, like ["spec","containers",0,"image"].
pathsoutputs the path of every node;paths(type == "number")filters by a predicate on the value;leaf_pathsispaths(scalars).path(expr)gives the paths an expression would access:path(..|select(type=="string" and test("secret"))).getpath(p),setpath(p; v)anddelpaths([p...])read and write by path array. Withreducethey enable generic transformations.to_entriesworks on one level;pathsworks on all of them.
walk(f) applies f to every node bottom-up, rebuilding the document: walk(if type == "object" then del(.managedFields) else . end) strips a key everywhere, and walk(if type == "string" then ascii_downcase else . end) lowercases all strings.
Combining them, you can flatten a document to dotted keys: [paths(scalars) as $p | {key: $p | map(tostring) | join("."), value: getpath($p)}] | from_entries. This is invaluable for diffing two large configs.
Example
cat > k.json <<'EOF'
{"spec":{"containers":[{"name":"app","image":"app:1.2"},{"name":"log","image":"fluent:2"}],
"meta":{"managedFields":[1],"labels":{"tier":"Web"}}}}
EOF
jq -c '[.. | .image? // empty]' k.json # ["app:1.2","fluent:2"]
jq -c '[paths(type == "string")] | .[0]' k.json # ["spec","containers",0,"name"]
jq -c 'getpath(["spec","meta","labels","tier"])' k.json # "Web"
jq -c 'setpath(["spec","replicas"]; 3) | .spec.replicas' k.json # 3
jq -c 'walk(if type == "object" then del(.managedFields) else . end) | .spec.meta' k.json
# {"labels":{"tier":"Web"}}
jq -c '[paths(scalars) as $p | {key: ($p | map(tostring) | join(".")), value: getpath($p)}]
| from_entries | keys[:2]' k.json
# ["spec.containers.0.image","spec.containers.0.name"]Remember
- .. visits every nested value; combine with ? and // empty
- paths, getpath, setpath, delpaths work with path arrays
- walk(f) transforms every node bottom-up
Watch out for
- Using .. | .field without ?, which errors as soon as it reaches an array or scalar.