JSONPath Cheatsheet

Last updated 21 September 2026 · Examples runnable in the JSONPath tester

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:

{ "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "price": 8.99, "isbn": "0-553-21311-3" }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "price": 22.99, "isbn": "0-395-19395-8" } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10 }

Structure operators

ExpressionResult
$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[*].authorAll 4 authors
$..authorAll 4 authors, found at any depth
$..price5 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

ExpressionResult
$.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.length4

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.

ExpressionResult
$..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.

BehaviourVaries how
Single vs double quotes in filtersRFC 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 valueSome 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 structuresOrdering of recursive-descent results is unspecified in older implementations.
Type coercion in comparisonsSome 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:

# every pod name and its node, one per line kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.nodeName}{"\n"}{end}'

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