JSONPath Queries: Finding Data in Big JSON Files
An API returns 6,000 lines of JSON. You need one field from every element of a nested array. You can write a loop with three levels of null checks, or you can write $.data[*].shipping.address.postcode and be done.
That is JSONPath: a compact query language for pulling values out of a JSON document, in the same spirit as XPath for XML. It is worth an hour of your time if you regularly work with large API responses, log records, or configuration files. This post covers the syntax that matters in practice, with worked examples. For JSON's own rules, The Complete Guide to JSON is the reference.
A document to query
Every example below runs against this:
{
"store": {
"name": "Bookline",
"books": [
{ "title": "Dune", "author": "Herbert", "price": 12.99, "tags": ["scifi"] },
{ "title": "Neuromancer", "author": "Gibson", "price": 9.50, "tags": ["scifi", "cyberpunk"] },
{ "title": "Ubik", "author": "Dick", "price": 22.00, "tags": ["scifi"] }
],
"bicycle": { "colour": "red", "price": 199.00 }
}
}
The core syntax
Every expression starts at $, the root of the document. From there you chain selectors.
Child selector. $.store.name returns "Bookline". The bracket form $['store']['name'] is equivalent and is what you need when a key contains a dot, a space, or a hyphen — $.user-id is parsed as a subtraction, $['user-id'] is not.
Index selector. $.store.books[0].title returns "Dune". Indices are zero-based, and negative indices count from the end, so $.store.books[-1].title gives "Ubik".
Wildcard. $.store.books[*].author returns ["Herbert", "Gibson", "Dick"] — one result per element. This is the selector that earns JSONPath its keep: extracting a field from every item in a collection is a single expression instead of a loop.
Recursive descent. $..price searches every level of the document and returns [12.99, 9.50, 22.00, 199.00] — the three book prices and the bicycle price, because .. does not care about depth or position. Powerful and blunt: use it when you do not know where a field lives, and tighten it once you do. $.store.books..price scopes the recursion to the books.
Slice. $.store.books[0:2] returns the first two books. The syntax mirrors Python: [start:end:step], end exclusive, and any part optional. $.store.books[::2] takes every second book.
Union. $.store.books[0,2] selects specific indices, and $['name','colour'] selects multiple keys, returning both matches.
Filter expressions
Filters are where JSONPath goes from convenient to genuinely useful. Inside [?...], the symbol @ refers to the element currently being tested.
$.store.books[?@.price < 10]— the one book under ten$.store.books[?@.author == 'Gibson']— matching on a string field$.store.books[?@.price > 10 && @.author != 'Dick']— combined with&&,||,!$.store.books[?@.discount]— existence test: elements that have adiscountkey at all
RFC 9535 also defines a small set of functions, the most useful being length(), count(), match() and search() for regular expressions. $.store.books[?length(@.tags) > 1] returns only Neuromancer.
One syntax note that causes confusion. The original 2007 proposal wrote filters with parentheses — [?(@.price > 10)] — and most existing libraries expect that form. RFC 9535 makes the parentheses optional. If an expression fails in one tool and works in another, this is usually why, and adding the parentheses is the safe portable choice.
Comparisons are type-aware: @.price > 10 compares numbers, and comparing a number to a string simply yields no match rather than an error. Note also that string literals inside filters use single quotes, which is legal in the query language even though single-quoted strings are never valid inside a JSON document itself.
Where the implementations disagree
JSONPath spent seventeen years as an informal proposal before RFC 9535 standardised it in 2024, and the ecosystem still reflects that. Known divergences between libraries include:
- Whether the result of a single-match query is the value or a one-element array
- How
$..[*]orders its results - Whether unsupported syntax raises an error or silently returns nothing
- Support for parent (
^) and script (()) extensions that some libraries add and the RFC does not include
The practical consequence: test your expression against the actual library you will use in production, not against a general reference. An expression that works in a browser tool may behave differently in Java's Jayway implementation or Python's jsonpath-ng.
Building an expression that works
The workflow that saves the most time is iterative rather than analytical.
Start by making the document readable. Paste it into a JSON Formatter so the structure is indented, then open it in a JSON Tree Viewer and collapse everything. Expand only the branch containing your target field — the path you clicked through is your JSONPath expression, one selector per level.
Then build the query incrementally in a JSONPath Query Tester. Start with $, confirm you get the whole document, and add one selector at a time, checking the match count after each. When the count drops to zero, the selector you just added is wrong — usually a misremembered key name, a wildcard where an index was needed, or a filter comparing a number to a string. This takes about ninety seconds and it beats reasoning about a path you have not verified.
Because that tester runs entirely in your browser, you can iterate against the real payload — a production API response with live tokens and customer data in it — rather than a redacted sample that may not even have the field you are hunting for. Nothing is uploaded, so there is no reason to sanitise first.
When to reach for something else
JSONPath selects. It does not transform, aggregate, or construct. Once you need to rename fields, compute a sum, join two arrays, or emit a differently shaped document, you have outgrown it.
jq is the usual next step: a complete transformation language with its own syntax, standard in shell pipelines and worth learning if you do this daily. JMESPath sits between the two, offering projections and multiselects with a stricter spec, and is what AWS CLI's --query flag uses. JSON Pointer (RFC 6901) is the opposite direction — it identifies exactly one location and has no wildcards or filters, which makes it the right tool inside APIs and JSON Patch operations where ambiguity is unacceptable.
If XPath is the comparison on your mind, JSONPath is deliberately smaller. XPath carries axes, namespaces, and a large function library because XML documents need them; JSON's simpler data model does not, and the difference in query-language weight mirrors the broader format comparison in JSON vs XML.
Expressions worth memorising
| Expression | Returns |
|---|---|
$..* |
Every value in the document |
$.data[*].id |
One field from every element |
$..items[?@.active == true] |
Filtered elements at any depth |
$.results[0:10] |
First ten elements |
$..[?@.error] |
Anything anywhere carrying an error field |
$['odd-key']['with.dot'] |
Keys that break dot notation |
The last one is worth internalising early. The first time a key contains a hyphen and your expression silently returns nothing instead of erroring, bracket notation is the fix — and knowing that saves an afternoon.
Frequently asked questions
What is JSONPath?
JSONPath is a query language for JSON, inspired by XPath. An expression describes a path through a document and returns every value that matches, so one line can extract a field from every element of a nested array.
What does $ mean in JSONPath?
The dollar sign is the root identifier — every JSONPath expression starts with it, and it refers to the whole document being queried. Inside a filter expression, @ refers to the current item being tested instead.
What is the difference between . and .. in JSONPath?
A single dot is a child selector that looks one level down. Two dots is a recursive descent that searches every level below the current node, so $..price finds every price field anywhere in the document.
Is JSONPath a standard?
It became one in 2024 with RFC 9535, which formalised the syntax and semantics. Before that it was a 2007 blog proposal implemented slightly differently by every library, which is why older implementations disagree on edge cases.
How do I filter a JSON array by a field value?
Use a filter selector: $.items[?@.price > 20] returns every element whose price exceeds 20. Older libraries require the parenthesised form $.items[?(@.price > 20)], which RFC 9535 also accepts.
Is JSONPath the same as jq?
No. JSONPath only selects existing values. jq is a full transformation language that can reshape, aggregate, and construct new JSON, so it is more powerful but also a bigger language to learn.
Does JSONPath work in the browser?
Yes, via small JavaScript libraries, which is how a client-side JSONPath tester evaluates expressions locally without sending your document to a server.
Try the related tools
JSON Formatter & Beautifier
Format, indent, validate, and beautify JSON with custom spacing.
JSON Tree Viewer
Visualize JSON hierarchy with collapsible outline tree view.
JSONPath Query Tester
Query and extract elements from complex JSON data using JSONPath syntax.
Related articles
The Complete Guide to JSON
A complete JSON guide for developers: syntax rules, data types, escape characters, common errors, minifying vs beautifying, and how JSON works in APIs.
What Is JSON? A Beginner's Explanation
What is JSON? A plain-English explanation of JSON meaning, syntax, and why every API uses it — with a real example and answers to the questions beginners ask.
JSON vs XML: Which Data Format Should You Use?
JSON vs XML compared head-to-head on size, parsing speed, schemas, tooling and API fit — plus the specific cases where XML still beats JSON in 2026.