How to Use JSONPath to Find Values in Large JSON Responses
JSONPath lets you point at a value buried inside a JSON document with a compact expression instead of scanning nested objects and arrays by hand. If you work with APIs, webhooks, logs, or configuration data, it is the fastest way to say "give me every price" or "the email of the user whose role is admin."
But there is a wrinkle that trips up almost everyone who moves between tools: for most of its life, JSONPath was not one language — it was a loose family of dialects that quietly disagreed with each other. This guide covers the syntax you will actually use, and, just as importantly, the places where two "JSONPath" implementations will hand you different answers for the same query.
What JSONPath is — and the 17-year standardization gap
JSONPath is a query language for selecting values out of JSON, conceptually the JSON cousin of XPath for XML. It began not as a standard but as a 2007 blog post by Stefan Goessner. That article was influential enough that dozens of libraries implemented it — in JavaScript, Java, Python, Go, and more — but because the post left edge cases undefined, every implementation filled the gaps differently.
It took until February 2024 for the IETF to publish RFC 9535, the first real standard, roughly 17 years after the original article. That gap matters because a huge amount of existing code, documentation, and tooling predates the standard and still behaves the old, divergent way. When a JSONPath query "works in one tool but not another," this history is almost always why.
A JSON document to query
Every example below runs against this small store document. The ideas scale directly to large API responses.
{
"store": {
"book": [
{ "title": "Clean Code", "price": 30, "author": "Robert C. Martin" },
{ "title": "Refactoring", "price": 45, "author": "Martin Fowler" }
],
"bicycle": {
"color": "red",
"price": 120
}
}
}
The core syntax
RFC 9535 defines a small set of selectors. These are the pieces you combine to build any query:
$— the root of the document; every query starts here..nameor['name']— a child by key. The bracket form is required when a key contains spaces, dots, or other awkward characters.*— a wildcard matching every child of an object or array...— recursive descent, matching at any depth below the current node.[start:end:step]— an array slice, borrowed from Python semantics (end is exclusive).[?<expr>]— a filter selector that keeps only nodes matching a condition.@— the current node, used inside a filter expression.
Walking the tree
$.store.bicycle.color → "red" $.store.book[0].title → "Clean Code" $.store.book[*].title → ["Clean Code", "Refactoring"] $..price → [30, 45, 120] (every price, any depth)
Notice that $..price reaches both the book prices and the bicycle price because recursive
descent does not care how deeply a key is nested.
Array slices
Slices follow Python's rules, including negative indexes and a step. This is far more expressive than a
single [0]:
$.store.book[0:1] → the first book only (end index is exclusive) $.store.book[-1:] → the last book $.store.book[::2] → every second book
Filters — the part beginners skip
Filters are where JSONPath stops being a fancy accessor and becomes a query language. Inside
[?...], @ refers to the item currently being tested, so you can select array
elements by their contents rather than their position:
$.store.book[?@.price < 40] → the "Clean Code" object (price 30)
$.store.book[?@.author == "Martin Fowler"].title
→ ["Refactoring"]
$.store.book[?@.price] → every book that HAS a price field
That last form is an existence test: a bare @.price is true whenever the
field is present, which is how you filter out records that are missing a key entirely.
Where implementations disagree
Here is the practical payoff of knowing the history. If you copy a JSONPath expression from one tool into another and get a surprising result, it is usually one of these long-standing divergences — the exact ambiguities RFC 9535 was written to settle:
- Single value vs. list. Some engines — notably the popular Java implementation, Jayway — always return a list even when exactly one node matches, while others unwrap a single match into a scalar. Code that assumes one shape breaks against the other. RFC 9535 resolves this by declaring that a query always produces a nodelist.
- Result ordering. The order of results from
..and wildcards was never guaranteed across implementations, so two engines could return the same values in a different order. If your code relied on position, it was relying on luck. - Filter and comparison semantics. Operators, type coercion, and what happens when you compare a missing field varied widely. The standard now pins down comparison and existence rules explicitly.
- Missing paths. Querying a path that does not exist returns an empty result in some engines and throws or returns null in others.
The takeaway: if a JSONPath query is part of a contract — a test assertion, a config extraction, a pipeline step — check whether your library conforms to RFC 9535, and do not assume a query validated in a browser tool behaves identically in a Java or Python service.
JSONPath vs JSON Pointer vs JMESPath
JSONPath is not the only way to address JSON, and picking the right one saves a lot of grief.
JSON Pointer (RFC 6901)
JSON Pointer is exact-path only — no wildcards, no filters, no recursion. A pointer like
/store/book/0/title identifies exactly one location and nothing else. That strictness is a
feature: it is unambiguous, so it is what JSON Patch (RFC 6902) uses to say precisely
which node to add, remove, or replace. Reach for JSON Pointer when you need one deterministic address, not
a search.
JMESPath
JMESPath is a separate, also-popular query language (it powers the --query flag in the AWS
CLI, among other things). It has its own syntax and, notably, first-class projections
and the ability to reshape output into new structures — something classic JSONPath does not do. It is a
different language, not a JSONPath dialect, so its expressions are not interchangeable.
Rule of thumb: use JSON Pointer when you need one exact node, and JSONPath or JMESPath when you need to search, filter, or match many nodes.
A reliable workflow
Most "JSONPath doesn't work" problems are really "I misread the structure" problems — a query against a key that lives one level deeper than you thought, or against invalid JSON that never parsed. Validate and format first so the shape is obvious, then build the query incrementally:
- Validate the JSON — a missing comma or unclosed brace makes every query fail or misbehave.
- Format it so nesting levels are visible at a glance.
- Start with a broad path (
$..name) to confirm the field exists anywhere. - Tighten it to an exact path (
$.items[*].name) once you can see the structure. - Add a filter (
[?@.active == true]) only after the path itself is correct.
Useful ToolzYard tools
Conclusion
JSONPath earns its place because a filter like $.book[?@.price < 40] replaces a page of
traversal code. The syntax is small — root, child, wildcard, recursive descent, slice, filter — and once
filters click it becomes a genuine query language rather than a fancy accessor.
The one thing to carry with you: JSONPath was a loose collection of dialects until RFC 9535 standardized it in February 2024, so single-value-vs-nodelist behavior, result ordering, and filter semantics can differ between libraries. When a query matters, confirm your implementation is RFC 9535 conformant, and reach for JSON Pointer instead when you need one exact, unambiguous address.
Frequently Asked Questions
Is JSONPath the same as XPath?
No. JSONPath is designed for JSON, while XPath is designed for XML. They are similar in concept but use different syntax and target different data formats.
Do I need valid JSON before using JSONPath?
Yes. JSONPath only works properly when the input JSON structure is valid.
Can JSONPath search nested objects deeply?
Yes. Recursive patterns like $..keyName can search for matching keys deep inside nested
objects and arrays.
What does $ mean in JSONPath?
The $ symbol represents the root of the JSON document.
Can JSONPath work with arrays?
Yes. JSONPath supports array indexes like [0] and wildcards like [*] to
work with array data.