You've got a 200-line JSON response. You need the email of every user whose lastLogin is older than 30 days, but only from the team department, sorted by id. You start writing a .filter().map() chain and twenty minutes later you're debugging an undefined because one user's meta object is missing.
This is what JSONPath was built for. It's a query language for JSON, the way XPath is for XML. You can write $.users[?(@.lastLogin < @.threshold)].email and skip the imperative gymnastics.
This is the cheat sheet I keep open while writing API tests, debugging GraphQL responses, and combing through CloudWatch logs. Real syntax, real payloads, the operators that actually show up in practice, and the gotchas that catch people who learned JSONPath from one tutorial.
The payload we'll use throughout
{
"store": {
"books": [
{ "category": "fiction", "author": "Tolkien", "title": "The Hobbit", "price": 14.99, "tags": ["fantasy", "classic"] },
{ "category": "fiction", "author": "Le Guin", "title": "A Wizard of Earthsea", "price": 12.50, "tags": ["fantasy"] },
{ "category": "tech", "author": "Knuth", "title": "TAOCP Vol 1", "price": 89.00, "tags": ["algorithms"] },
{ "category": "tech", "author": "Crockford", "title": "JavaScript: The Good Parts", "price": 22.00 }
],
"bicycle": { "color": "red", "price": 399.00 }
}
}
Real-world enough to show off the operators, small enough to keep in your head.
The core syntax (you'll use these every day)
| Symbol | Meaning |
|---|---|
$ |
The root |
. or []
|
Child accessor |
.. |
Recursive descent (find anywhere) |
* |
Wildcard (all children) |
[n] |
Array index |
[start:end] |
Array slice |
[?(...)] |
Filter expression |
@ |
The current node (inside a filter) |
That's roughly the whole language. Everything else is composition.
Walking through real queries
All books:
$.store.books[*]
The [*] is the wildcard form. $.store.books works too if you want the whole array as one result.
The first book's title:
$.store.books[0].title
The last book's title:
$.store.books[-1].title
Negative indices count from the end, like Python. Not all implementations support this — the original Goessner spec didn't, but jsonpath-plus and jq do.
Every author in the document:
$..author
The .. is recursive descent. It searches every depth for a key called author. Useful when you don't know exactly where something lives.
All books with price under $20:
$.store.books[?(@.price < 20)]
The @ inside the filter refers to the current item being checked. This is the operator that makes JSONPath worth learning — it does in one line what takes 5–10 lines of imperative JavaScript.
Just the titles of those books:
$.store.books[?(@.price < 20)].title
Filters compose with subsequent path segments. Cleaner than chaining .filter().map().
Books that have a tags field at all:
$.store.books[?(@.tags)]
A bare @.field evaluates truthy if the field exists. Useful for sparse data.
Books tagged 'fantasy':
$.store.books[?(@.tags && @.tags.indexOf('fantasy') >= 0)]
Or in implementations that support in:
$.store.books[?('fantasy' in @.tags)]
This is where implementations diverge. Some support in, some require indexOf, some have a contains helper. RFC 9535 (the new official spec) standardizes a lot of this, but most libraries you'll touch still follow Goessner's original 2007 draft. Test before you commit.
The first two books (slice):
$.store.books[0:2]
Same syntax as Python slicing. [start:end:step] is also valid in most implementations: $.store.books[::2] for every other book.
Filter operators worth knowing
Inside a [?(...)], you can use:
-
==,!=,<,<=,>,>=— comparisons -
&&,||,!— logical operators -
=~— regex match (in some implementations)
Real example — books over $50 OR by Tolkien:
$.store.books[?(@.price > 50 || @.author == 'Tolkien')]
Author names matching a pattern:
$.store.books[?(@.author =~ /^[A-K]/)]
That last one is library-specific. jsonpath-plus supports it; the standard jsonpath package on npm doesn't. If your filter doesn't work, the regex operator is the first thing I'd suspect.
The Goessner / RFC 9535 / jq divergence
Three implementations dominate, and they disagree:
Goessner JSONPath (2007). The original. Most JS libraries follow this. Filters use JavaScript-like syntax. .. recursive descent. No standardized output format.
RFC 9535 (2024). The official IETF standard. Stricter syntax, type-safe filters, no JavaScript expressions, well-defined edge cases. Adoption is still slow — most production tools haven't migrated.
jq. Not actually JSONPath, but does the same job with different syntax. .store.books[] | select(.price < 20) is the equivalent of the filter we wrote above. jq is its own world; if you're already comfortable with it, you don't need JSONPath.
The practical advice: pick a JSONPath library, read its docs, don't assume queries port. The 80% that's identical is the 80% in this cheat sheet.
Where you'll actually use JSONPath
It's not just for browser debugging. JSONPath shows up in:
-
Postman / Insomnia / Bruno — for assertions in API tests (
pm.response.json()returns the body, then JSONPath filters it). - Datadog / Grafana — for extracting values from JSON log entries.
-
kubectl —
kubectl get pods -o jsonpath='{.items[*].metadata.name}'. -
AWS CLI —
aws ec2 describe-instances --query. Note: AWS uses JMESPath, not JSONPath. They look similar, they aren't. - Browser fetch hooks — middleware that extracts a specific path from every API response.
- GraphQL response shaping — though GraphQL has its own query syntax, JSONPath is useful for post-processing.
The skill compounds. Learn it once, use it in five tools.
Common mistakes
1. Forgetting @ inside filters. $.books[?(.price < 20)] is wrong; it should be $.books[?(@.price < 20)]. Easy mistake when you're used to dot-prefixed paths.
2. Quoting numeric comparisons. [?(@.price < '20')] does string comparison, not numeric. The result of comparing strings to numbers is implementation-defined. Drop the quotes.
3. Assuming results are an array. A query that returns one match returns the value, not a one-element array, in some libraries. Always handle both: Array.isArray(result) ? result : [result]. RFC 9535 standardizes around always-an-array; legacy libraries don't.
4. Using .. in a hot loop. Recursive descent walks the entire tree. On a 50,000-line CloudWatch payload, it's measurably slow. Prefer explicit paths when you know the structure.
5. Forgetting that filters short-circuit on missing fields. [?(@.price > 20)] excludes items where price is missing. If you wanted to include them, write [?(!@.price || @.price > 20)].
The fastest way to test queries
When I'm writing JSONPath against a real payload, I don't iterate in code. The feedback loop is too slow. The JSONPath tester on json.renderlog.in lets you paste the JSON, type the query, and see results live — including syntax errors highlighted as you type. The whole thing runs client-side, so production payloads with sensitive data don't get uploaded anywhere.
For one-off transformations of the JSON itself before you query it, the JSON formatter and JSON validator are part of the same workflow. Bookmark all three.
TL;DR
-
$is root,@is current,..is recursive descent,[?(...)]is a filter. - 80% of your queries are
$.path[?(@.field op value)].subfield. - Pick one library and stick to it; queries don't always port between Goessner, RFC 9535, and jq.
- Test queries in a browser-based JSONPath tester before wiring them into code.
- For nested-JSON debugging in general, the same sites usually have a JSON formatter and diff tool — bookmark them together.
JSONPath isn't elegant. It's a fragmented, half-standardized language with three competing implementations and a syntax that looks like 2007 PHP. But it solves a real problem in one line that takes ten lines of .filter().map().reduce() chains, and that's worth learning.
If this was useful, I've also built a handful of other free, browser-based tools — no signup, no uploads, everything runs client-side:
- JSON Tools — https://json.renderlog.in (formatter, validator, JWT decoder, JSONPath tester, 40+ converters)
- Text Tools — https://text.renderlog.in (case converters, slug generator, HTML/markdown utilities, 70+ tools)
- PDF Tools — https://pdftools.renderlog.in (merge, split, OCR, compress to exact size, 40+ tools)
- Image Tools — https://imagetools.renderlog.in (compress, convert, resize, background remover, 50+ tools)
- QR Tools — https://qrtools.renderlog.in (WiFi, vCard, UPI, bulk QR codes with logos)
- Calc Tools — https://calctool.renderlog.in (60+ calculators for finance, health, math, dates)
- Notepad — https://notepad.renderlog.in (private, offline-first notes, no signup)
Top comments (0)