A worked example: a Kubernetes pod list
This is a trimmed kubectl get pods -o json response with two pods - a web server that is running, and a worker with a log-shipping sidecar that is stuck in Pending after four restarts. Pod lists are a good test for a JSONPath generator: they nest arrays inside arrays, and their labels use keys like app.kubernetes.io/name that break naive dot notation.
{
"apiVersion": "v1",
"kind": "List",
"items": [
{
"metadata": {
"name": "web-7d4b9c-x2k8p",
"namespace": "shop",
"labels": { "app.kubernetes.io/name": "web", "tier": "frontend" }
},
"spec": {
"nodeName": "node-a",
"containers": [
{ "name": "web", "image": "shop/web:2.4.1", "ports": [{ "containerPort": 8080 }] }
]
},
"status": {
"phase": "Running",
"podIP": "10.1.4.17",
"containerStatuses": [{ "name": "web", "ready": true, "restartCount": 0 }]
}
},
{
"metadata": {
"name": "worker-5f6c8-9qzv4",
"namespace": "shop",
"labels": { "app.kubernetes.io/name": "worker", "tier": "backend" }
},
"spec": {
"nodeName": "node-b",
"containers": [
{ "name": "worker", "image": "shop/worker:2.4.1" },
{ "name": "log-shipper", "image": "fluent/fluent-bit:3.1" }
]
},
"status": {
"phase": "Pending",
"containerStatuses": [{ "name": "worker", "ready": false, "restartCount": 4 }]
}
}
]
}
With indices collapsed, the generator returns one wildcard expression per distinct field:
$.apiVersion
$.kind
$.items
$.items[*].metadata
$.items[*].metadata.name
$.items[*].metadata.namespace
$.items[*].metadata.labels
$.items[*].metadata.labels['app.kubernetes.io/name']
$.items[*].metadata.labels.tier
$.items[*].spec
$.items[*].spec.nodeName
$.items[*].spec.containers
$.items[*].spec.containers[*].name
$.items[*].spec.containers[*].image
$.items[*].spec.containers[*].ports
$.items[*].spec.containers[*].ports[*].containerPort
$.items[*].status
$.items[*].status.phase
$.items[*].status.podIP
$.items[*].status.containerStatuses
$.items[*].status.containerStatuses[*].name
$.items[*].status.containerStatuses[*].ready
$.items[*].status.containerStatuses[*].restartCount
Three things in that list are worth a closer look.
- The label key is quoted.
$.items[*].metadata.labels.app.kubernetes.io/namewould walk into a key calledapp, thenkubernetes, and so on, and match nothing. The bracket form['app.kubernetes.io/name']reads one key whose name contains dots and a slash. - Wildcards stack.
$.items[*].spec.containers[*].imagecrosses two arrays, so it returns every image of every container of every pod - three values here, because the worker pod has a sidecar. - Fields that exist on only some elements still appear.
podIPis listed because the running pod has one. Evaluated against this document, it returns a single value, not two - aPendingpod has not been given an IP yet, and a wildcard query simply skips elements where the field is missing.
Every expression above was evaluated against this document with two libraries - jsonpath-plus 11.1 and the RFC 9535 implementation jsonpath-rfc9535 1.3 - and both returned the same results.
Wildcards or literal indices?
Untick Collapse array indices and each element gets its own address instead: $.items[0].metadata.name, $.items[1].spec.containers[1].image, and so on. Use the wildcard form when you are writing a query that should work on any response. Use the indexed form when you are debugging one specific record and need the exact location of a value - for instance, to confirm that it is the second container of the second pod that has the problem.
Using the output with the tools you already have
JSONPath has been around since 2007, but it was only standardised in February 2024 as RFC 9535. Most tools you will meet were written before that, and each speaks a slightly different dialect. The generated expressions use the most portable subset - dot members, quoted bracket members, [*], and numeric indices - but the syntax around them differs:
| Tool | The same query | What changes |
|---|---|---|
| JSONPath libraries | $.items[*].metadata.name | Nothing - use the generated expression as is. |
| kubectl | kubectl get pods -o jsonpath='{.items[*].metadata.name}' | Wrapped in {}, and the leading $ is optional. |
| kubectl, dotted key | '{.items[*].metadata.labels.app\.kubernetes\.io/name}' | kubectl escapes dots in a key with a backslash rather than quoting it. |
| jq | .items[].metadata.labels["app.kubernetes.io/name"] | jq is not JSONPath: no $, [] instead of [*], and double quotes. |
The jq column is worth checking for yourself - against the example above, that command prints web and worker, one per line.
Adding a filter by hand
A generator can tell you where fields are, but not which records you care about. For that you add a filter. To get the names of only the running pods:
# RFC 9535 syntax
$.items[?@.status.phase == 'Running'].metadata.name
# Older syntax, from the original 2007 JSONPath article
$.items[?(@.status.phase == 'Running')].metadata.name
Both return ["web-7d4b9c-x2k8p"] in an RFC 9535 implementation. The trap is going the other way. Run the RFC form through jsonpath-plus 11.1, a popular JavaScript library that predates the standard, and it does not raise an error - it returns an empty array. A query that silently matches nothing looks exactly like data that has nothing to match, so if a filter comes back empty, try the parenthesised form before concluding the records are not there.
Dot notation vs JSONPath
These are related but not the same thing. Dot notation (items[0].metadata.name) describes where a value sits, and is what you write in JavaScript or Python source. JSONPath ($.items[*].metadata.name) is a query language: $ marks the document root, and one expression can match many values at once. If you want accessor paths to paste into code rather than queries, use the dot-notation flattener instead.
How the generator decides when to quote a key
A key is written after a dot only when it looks like an identifier: letters, digits, _ and $, not starting with a digit. Anything else is written as ['key'], with any single quote in the key escaped. That is stricter than RFC 9535 requires - the standard also accepts non-ASCII letters after a dot - but it keeps the output valid in older libraries that do not.
$.metadata.name an identifier
$.metadata.labels['app.kubernetes.io/name'] contains dots and a slash
$.headers['content-type'] a hyphen
$.scores['2fa'] starts with a digit
Other output formats
- Flatten JSON to dot notation - accessor paths for code and field mappings
- Convert JSON to a TypeScript interface
- View JSON as an indented tree
- Format or minify the JSON - whitespace only, every value kept exactly
Further reading
- Understanding nested JSON objects and arrays
- Processing large JSON files without running out of memory - when a jq query needs
--stream - How to extract all keys from a JSON object
Written and maintained by Ashish Singh · Last updated · Changelog · Found a problem with this page? Tell me.