JSONPath is a query language for JSON the way XPath is for XML: you point at nodes instead of writing nested loops. Most days you only need a handful of tokens - `$`, `@`, dots, brackets, `*`, `..`, a slice, and a simple `[?()]` filter. This guide is that short list, not a full RFC clone. Paste the sample below into the JSONPath Tester and try each expression in your browser - nothing uploads.
Quick answer
Start every path with `$` (the document root). Use `@` inside a filter for the current item. Prefer `$.store.book[*].title` for everyday work; use `$['store']['book'][0]` when a key has spaces or reserved characters. `$..author` finds a field at any depth. Filters look like `$.store.book[?(@.price < 10)]`. Treat RFC 9535 as the modern dialect and older Goessner samples as folklore until you verify them.
One sample payload
Every example below uses the classic bookstore document (the same shape many JSONPath tutorials share). Keep it open while you read:
- `store.book` - an array of objects with `category`, `author`, `title`, `price`, and sometimes `isbn`.
- `store.bicycle` - a nested object with `color` and `price`.
- If your payload arrived as one line, pretty-print it first with the JSON Minifier & Formatter, then query.
$ and @
`$` is the root of the JSON value you pasted - the whole object or array. Every useful expression starts there. `@` is only meaningful inside a filter: it means "the current element being tested." Outside a filter, `@` is not a free-floating cursor.
- `$` - the document root.
- `@.price` inside `[?()]` - the `price` field of the item under test.
- `$.store.book[?(@.isbn)]` - books that have an `isbn` key (existence check).
- A bare path without `$` is usually rejected by strict testers - always lead with `$`.
Dot vs bracket
Dot notation is shorthand for simple identifiers: `$.store.book`. Bracket notation is the explicit form: `$['store']['book']`. Use brackets when the key is not a plain identifier (spaces, hyphens, leading digits) or when you want the RFC 9535 normalized path style that many tools print in match tables.
- `$.store.bicycle.color` - fine for ordinary keys.
- `$['content-type']` - required when the key is not a JS-like identifier.
- `$['store']['book'][0]['title']` - singular normalized path to one node (RFC 9535 §2.7 style).
- Indexes are always brackets: `$.store.book[0]`, not `$.store.book.0`.
* and ..
`*` is a wildcard for "every child at this level." `..` is recursive descent: walk the tree and collect matches at any depth. Wildcards stay shallow; descent can surprise you on large documents.
- `$.store.book[*].title` - every book's title (one level of array items).
- `$.store.*` - both `book` and `bicycle` under `store`.
- `$..author` - every `author` anywhere under the root.
- `$..price` - book prices and the bicycle price together - tighten the path if you only wanted one.
Slices
Array slices use Python-like `start:end:step` inside brackets. Omitted ends mean "from the start" or "to the end." Negative indexes count from the end when the implementation supports them.
- `$.store.book[0:2]` - the first two books (indexes 0 and 1).
- `$.store.book[-1:]` - the last book, on engines that allow negative indexes.
- `$.store.book[::2]` - every other book when step is supported.
- Slices select array items; chain a field after them: `$.store.book[0:2].title`.
A simple filter
Filters sit in `[?(...)]` and keep items where the expression is true. `@` is the current item. Comparisons usually do not coerce types the way loose JavaScript does - `1 == "1"` is often false. Keep filters boring: equality, inequality, numeric compare, `&&`, `||`, `!`, and existence.
- `$.store.book[?(@.price < 10)]` - cheap books.
- `$.store.book[?(@.price < 10 && @.category == 'fiction')].title` - filter, then extract a field.
- `$.store.book[?(@.isbn)]` - items that have `isbn`.
- Put the filter on the array you are scanning, not on a lone object root unless the root itself is an array.
RFC 9535 vs older Goessner dialect
Stefan Goessner's 2007 article popularized JSONPath and many libraries still copy its examples. RFC 9535 (2024) is the IETF standard: clearer grammar, normalized paths, and fewer "eval anything" escape hatches. Script selectors such as `[(@.length-1)]` and free-form JavaScript inside filters belong to the old wild west, not to a safe browser tester.
- RFC 9535 - standardized operators, normalized paths like `$['store']['book'][0]`, designed for interoperable implementations.
- Goessner-era samples - still everywhere in blog posts; verify before you ship them in production.
- No-eval subsets - what you want for pasted API payloads. Reject script selectors; never run the path as JavaScript.
- When two tools disagree, check which dialect they claim - not which one "feels" right.
Not a full spec
This article is a cheat sheet for expressions you type daily. For normative detail, read RFC 9535. For private tryouts against your own JSON, use the JSONPath Tester.
Five copy-paste examples
Against the bookstore sample, try these in order:
- `$.store.book[*].title` - all titles as a list.
- `$..author` - every author via recursive descent.
- `$.store.book[0:2]` - first two book objects.
- `$.store.book[?(@.price < 10)]` - books under price 10.
- `$.store.book[?(@.price < 10)].title` - same filter, titles only.
After you extract a flat list of records, you may want a spreadsheet view - pipe a JSON array of objects through the JSON to CSV Converter. For nested API blobs you are still exploring, stay in JSONPath until the shape is clear.
Frequently asked questions
Does every JSONPath start with $?
Yes in practice. `$` names the root. Strict tools reject paths that omit it.
When do I use @?
Only inside `[?()]` filters, as the current element. It is not a substitute for `$` at the start of a path.
Why did I get zero matches?
A valid path can match nothing. Common causes: filtering an object root instead of an array, a typo in a key, or expecting JavaScript type coercion. Try `$..field` to confirm the field exists, then tighten.
Is JSONPath the same as JMESPath or jq?
No. Related idea, different syntax and libraries. Pick one dialect per codebase and stick to it.
Can I test a private API response safely?
Yes if the evaluator runs in your browser with no upload. The JSONPath Tester is built for that - paste, query, copy extracted JSON or normalized paths.
Try it on ToolsMinify
Every tool below runs in your browser - free, no signup, nothing uploaded:
- JSONPath Tester - paste JSON, run an expression, copy matches or extracted values.
- JSON Minifier & Formatter - pretty-print or minify a payload before you query it.
- JSON to CSV Converter - turn a flat JSON array of objects into CSV after you extract it.
- All developer tools - the full category index.
Try it free
Open the JSONPath Tester, load the bookstore sample, and paste `$.store.book[?(@.price < 10)].title`. You should see the cheap titles - all on your device.