JSONPath Cheatsheet
JSONPath is the query language behind kubectl -o jsonpath, Postman test assertions, Azure CLI --query's cousin JMESPath, Jenkins pipeline utilities, Elasticsearch ingest processors and a hundred integration tools. It is small enough to learn in an afternoon and inconsistent enough between implementations to cost you one.
Every example below runs against this document:
Structure operators
| Expression | Result |
|---|---|
$ | The whole document |
$.store.bicycle.color | "red" |
$['store']['bicycle']['color'] | "red" — identical, required when a key contains a dot, dash or space |
$.store.* | The book array and the bicycle object — 2 results, not 5 |
$.store.book[*].author | All 4 authors |
$..author | All 4 authors, found at any depth |
$..price | 5 results — the 4 books and the bicycle |
$..* | Every value in the document, recursively |
The distinction between $.store.* and $..* is the one to internalise. A single * descends exactly one level. .. descends all of them, which is why $..price picks up the bicycle that $.store.book[*].price correctly excludes.
Array access
| Expression | Result |
|---|---|
$.store.book[0] | First book |
$.store.book[-1] | Last book |
$.store.book[0,2] | First and third — a union, not a range |
$.store.book[0:2] | Books 0 and 1 — end-exclusive |
$.store.book[1:] | Everything after the first |
$.store.book[:-1] | Everything except the last |
$.store.book[::2] | Every second book, from the start |
$.store.book.length | 4 |
Slices follow Python semantics exactly, including negative indices and the exclusive end. Unions use commas and take whatever you list, in the order you list it.
Filters
Inside [?(...)], @ refers to the element under test. The filter runs once per element and keeps the ones where it evaluates true.
| Expression | Result |
|---|---|
$..book[?(@.price < 10)] | Sayings of the Century, Moby Dick |
$..book[?(@.isbn)] | The two books that have an ISBN — existence test |
$..book[?(!@.isbn)] | The two that do not |
$..book[?(@.category == 'fiction')] | 3 books |
$..book[?(@.category == 'fiction' && @.price < 10)] | Moby Dick only |
$..book[?(@.author =~ /tolkien/i)] | The Lord of the Rings |
$..book[?(@.price > $.expensive)] | Comparing against the root — not supported everywhere |
The filter trap. A filter referencing a key that does not exist evaluates to false, not to an error. [?(@.pirce < 10)] returns zero results and reports no problem. If a filter returns nothing, check the spelling of the key before you question the operator.
Where implementations disagree
The original JSONPath article (Stefan Gössner, 2007) was a blog post with a reference implementation, not a specification. Every library since has filled the gaps differently. RFC 9535 finally standardised it in February 2024, but the ecosystem has not converged yet.
| Behaviour | Varies how |
|---|---|
| Single vs double quotes in filters | RFC 9535 allows both. Jayway (Java) prefers single; some strict parsers reject double. |
Regex operator =~ | Jayway and this tool support it. It is not in RFC 9535 and is absent from kubectl and many Python libraries. |
Root access inside a filter ($.expensive) | Jayway supports it. RFC 9535 does not permit it. Most JavaScript implementations do not. |
| A path matching exactly one value | Some libraries return the value, others a one-element list. Jayway's "definite vs indefinite path" rule decides this implicitly, which surprises people constantly. |
Script expressions [(@.length-1)] | In the 2007 original, dropped from RFC 9535. Avoid it; use [-1]. |
$..[0] on mixed structures | Ordering of recursive-descent results is unspecified in older implementations. |
| Type coercion in comparisons | Some coerce '5' and 5; RFC 9535 and this tool do not. |
Practical advice: keep expressions in the intersection of what every implementation supports — child access, wildcards, recursive descent, indices, slices and simple comparison filters. Everything beyond that is worth testing against the specific library you deploy with.
kubectl's JSONPath is its own dialect
If you reached this page from a kubectl problem, note that its implementation differs in ways that will waste an hour:
- The expression goes inside
{}:kubectl get pods -o jsonpath='{.items[*].metadata.name}' $is optional and usually omitted.- It adds
range/endfor iteration and\n/\tescapes, borrowed from Go templates. - Filters use
?()but support only==,!=and a handful of comparisons — no regex, no boolean chaining. - Keys containing dots must be escaped:
{.metadata.labels['app\.kubernetes\.io/name']}
JSONPath or jq?
They overlap but are not competitors. JSONPath selects; jq transforms. If you need to reshape output, compute values, group, or build new objects, jq is the right tool and JSONPath will fight you. If you need to pull a set of values out of a document — especially inside a config file, a test assertion or a tool that already speaks JSONPath — JSONPath is smaller and needs no external binary.
The rule of thumb: if your expression is getting long enough that you want a comment, you have outgrown JSONPath.
Related
- JSONPath tester — run every example on this page
- Every JSON.parse error explained
- JSON formatter and validator