
JSONPath in the Browser: A Working Reference and Tester for the Patterns You Actually Use
📷 Pixabay / PexelsJSONPath in the Browser: A Working Reference and Tester for the Patterns You Actually Use
Stop guessing whether your JSONPath query will match what you expect. A practical guide to filters, slices, and the recursive descent operator — with a free tester.
I've been working with APIs long enough to remember when XML was the default and XPath was the query language everyone learned. JSONPath is the JSON equivalent, and after years of avoiding it (because I could just write JavaScript, right?), I've come around. There are too many places where JSONPath is the only language available — Postman variables, AWS Step Functions, Kubernetes selectors, datadog filters — and being fluent saves real time.
This guide is the reference I wish I'd had when I started. We'll cover the syntax that actually shows up in production, the gotchas that cost me hours, and how the JSONPath Tester at ToolBox Hubs lets you check your expressions before pasting them anywhere consequential.
What JSONPath Is, Quickly
JSONPath is a query language for JSON documents. You give it an expression like $.store.book[*].author and it returns all the values that match. The $ is the root, the dots and brackets navigate into the structure, and special operators let you filter, slice, and traverse recursively.
If you've used XPath for XML, the mental model transfers directly. If you haven't, think of it as a more powerful version of dot notation in JavaScript — one that can express "all books with price under $10" or "every author at any depth in the document" in a single line.
The canonical example everyone uses comes from Stefan Goessner's original article:
{
"store": {
"book": [
{ "category": "fiction", "author": "Tolkien", "price": 22.99 },
{ "category": "reference", "author": "Rees", "price": 8.95 }
],
"bicycle": { "color": "red", "price": 19.95 }
}
}
$.store.book[*].author returns ["Tolkien", "Rees"]. $..price returns [22.99, 8.95, 19.95]. $.store.book[?(@.price < 10)] returns the second book object. That's the whole game.
The Operators You'll Use 90% of the Time
There are dozens of JSONPath operators. Five of them cover almost everything you'll write in practice.
$ — The Root
Every JSONPath expression starts with $. It refers to the top of the document. You can technically omit it (some libraries are lenient) but writing it explicitly makes intent clear.
.field — Child Access
Standard dot notation. $.users.0.email walks from root to the users array, picks index 0, and reads email. Same as JavaScript except the 0 works as a property name.
[n] — Array Index
Bracket notation for array access, useful when indices are dynamic or when the field name has special characters. $.users[0], $.users[-1] (last item), $.users[0:3] (slice from 0 to 3 exclusive).
Negative indices count from the end. $.users[-1] is the last user. $.users[-3:] gives the last three. This isn't supported in every implementation — RFC 9535 standardizes it, but check your library if you depend on it.
[*] — Wildcard
Match all items in an array or all values in an object. $.users[*].email returns every user's email. $.config.* returns every value in the config object. Combined with property access, this is how you flatten common structures.
..field — Recursive Descent
The most powerful operator and the one I use most. $..price finds every price field anywhere in the document, no matter how deeply nested. Useful when you don't know or care about the exact structure.
The descent doesn't care about path — it just finds everything matching the pattern. That makes it great for ad-hoc data exploration ("does this response contain any user IDs?") and dangerous if your data has the same key name appearing in different contexts (a name field on users and a name field on roles will both match $..name).
Filters: Where People Get Stuck
Filter expressions are written [?(...)] where the contents are a boolean expression evaluated against each item in an array. Inside the filter, @ refers to the current item.
$.store.book[?(@.price < 10)]
$.users[?(@.role == 'admin')]
$.events[?(@.timestamp > 1700000000)]
The @ is the part that trips people up. It's the equivalent of this or the current item in a forEach. Without it, the filter can't reference any field — [?(price < 10)] doesn't work because price is undefined; the engine doesn't know to look inside the current item.
Comparison Operators
Universal across libraries: ==, !=, <, >, <=, >=.
The right-hand side can be a literal: a number (@.age > 18), a single-quoted string (@.role == 'admin'), a double-quoted string, true, false, or null.
What's NOT universal: comparing two fields against each other (@.price > @.cost), regex matching (@.email =~ /.*@example\.com/), function calls like length() or match(). Some libraries support these; many don't. The JSONPath Tester at ToolBox Hubs supports the basic comparisons against literal values, which covers the vast majority of real queries.
Boolean Combinations
Standard implementations support && and || for combining conditions:
$.products[?(@.in_stock == true && @.price < 50)]
$.events[?(@.severity == 'error' || @.severity == 'critical')]
If you find yourself writing complex boolean logic in JSONPath, that's usually a signal to drop down to actual code — your future self reading the expression in six months will thank you. JSONPath shines for simple selections; complex business logic belongs in a real language.
Slices: For When You Need Just Some Items
[start:end:step] works exactly like Python slicing.
[0:5]— first 5 items[5:]— from index 5 onward[:3]— first 3 items (same as[0:3])[-3:]— last 3 items[::2]— every other item[::-1]— reversed (not supported everywhere)
Slicing is one of the cleaner parts of JSONPath. It's a direct port from Python and behaves predictably. I use it a lot for "give me the first N results" or "give me everything except the first one" patterns.
Patterns That Actually Show Up at Work
Theory is fine; let's look at the real queries.
Filtering API Responses
Your API returns paginated results and you want to find specific items. JSONPath in your test framework or Postman:
$.data[?(@.status == 'active')].id
Returns the IDs of all active items. If the API doesn't have a "filter by status" parameter, this is faster than transforming in code.
Extracting Values from Webhook Payloads
You're processing webhooks from Stripe, GitHub, or similar, and you need to grab specific fields. JSONPath in AWS EventBridge rules, Step Functions, or Logic Apps:
$.detail.payload.items[*].sku
Returns the SKU of every item in an order. Cloud workflow tools rely on JSONPath because it's declarative and inspectable, where embedded JavaScript would be a security and maintainability burden.
Finding Items in Deeply Nested Data
You don't know the exact structure but you know there's a user ID somewhere:
$..userId
Returns every userId field in the document, regardless of nesting depth. Useful for exploration when working with unfamiliar API responses or third-party integrations.
Validating Schema-Like Constraints
You need to check that all items in a list have a specific field:
$.users[?(!@.email)]
Returns users missing an email. If the result is empty, your data is clean. This pattern works well in CI test scripts where you parse a JSON output and assert on its structure.
The Gotchas (a Personal List)
These are the things that have cost me hours.
The @ in filters is mandatory. Writing [?(price < 10)] instead of [?(@.price < 10)] is the most common mistake. Without @, the engine doesn't know price refers to a field on the current item.
Filters return matching items, not the field you filtered on. $..book[?(@.price < 10)] returns book objects, not prices. To get just the prices: $..book[?(@.price < 10)].price.
Quotes matter inside expressions. Use single quotes inside JSONPath strings to avoid confusing the surrounding language. In a JSON config file your JSONPath is a string value, so the JSONPath itself uses single quotes: "$.users[?(@.role == 'admin')]". Mixing them up creates parse errors.
Recursive descent finds shadow matches. $..name will match every name field, including ones in unrelated nested objects. If you have user name fields and product name fields in the same document, this will return both. Be specific when it matters.
Index 0 vs. property "0". $.items[0] works on arrays. $.items.0 may or may not work — most parsers reject it for arrays (it's bracket-only) but accept it as a property name on objects. Stick with brackets for array access.
Empty results aren't errors. If your JSONPath matches nothing, you get an empty array [], not an exception. This is correct behavior, but it can hide typos. If you expected results and got none, double-check the path.
RFC 9535 vs. Goessner-Era Libraries
The original 2007 article that defined JSONPath was informal — it described syntax with examples but didn't formally specify behavior in edge cases. Different library implementations made different choices, and over the years the differences accumulated.
In 2024, RFC 9535 was published as the official spec. It standardizes syntax and semantics, particularly around:
- Filter expression grammar
- Function extensions (
length,count,match,search,value) - Normalized paths in output (each result includes its location)
- I-Regexp profile for regex matching
If you're starting a new project, prefer libraries that target RFC 9535 — jsonpath-rfc9535 for Node, jsonpath-rfc9535 Python ports, jp CLI tool. If you're maintaining existing code, you're likely on a Goessner-era implementation; that's fine, just know the differences exist.
The JSONPath Tester at ToolBox Hubs implements the most common operators that work consistently across implementations. It's intentionally conservative on filter syntax to avoid testing things that won't work in your target environment.
Where JSONPath Loses to jq
I'd be misleading you if I didn't mention jq.
JSONPath is good at finding values in JSON. jq is good at finding AND transforming JSON. If you need to:
- Project objects to a different shape (
.users | map({id, email})) - Aggregate values (
map(.price) | add) - Compose multiple operations
- Handle complex conditional logic
jq is dramatically better. JSONPath doesn't have aggregation, doesn't have map/reduce, and doesn't have function composition. These aren't bugs — they're scope decisions. JSONPath is a query language. jq is a transformation language.
The right tool depends on your environment. JSONPath is everywhere because it's simple to embed (no dependencies, just a string parser). jq requires a binary and is harder to use in browser contexts or constrained environments. In a Postman test or a Step Function input mapping, you only have JSONPath. In a shell pipeline or a server-side data transform, jq is usually better.
A Practical Workflow
Here's how I actually use JSONPath now:
-
Open the API response or JSON document in the JSON Formatter to make it readable.
-
Identify the value(s) I want to extract. Usually I can just trace the path mentally: "users array, item N, role field, equals admin."
-
Open the JSONPath Tester, paste the JSON, and write the expression. Iterate until it returns exactly what I want.
-
Copy the working expression into wherever it needs to live — Postman test, AWS Step Function, Kubernetes selector.
-
If I'm building something more complex (transformation, aggregation), I switch to jq.
This workflow cuts hours of "why isn't my JSONPath matching" debugging out of integrations. The tester is the missing step that most tutorials skip — they show the syntax and assume you can write it correctly first try. You usually can't, especially with filters.
Tool Recommendations for the Wider JSON Workflow
JSONPath is one tool in a JSON workflow. Pairing it with related utilities makes the whole pipeline smoother:
- JSON Formatter — pretty-print before you query; minified JSON is unreadable
- JSON Diff — when JSONPath returns different results between two responses, diff them to see what changed
- JSON to TypeScript — generate TypeScript types from a sample, then use the type to write JSONPath that matches the schema
- JSON Schema Generator — produce a JSON Schema for validation; complementary to JSONPath which is for query
The JSONPath Tester is free, browser-only, and never sends your JSON anywhere. Paste, query, iterate, copy. That's the loop.
Closing Notes
JSONPath isn't glamorous. It's not the language anyone gets excited about learning. But spending an hour to get fluent pays back across years of API debugging, cloud workflow configuration, and data exploration. The syntax is small enough to memorize the common patterns. The gotchas are predictable enough to internalize.
If you're new to it: start with the basic patterns ($.field, $..field, $.array[*], [?(@.field == 'value')]) and avoid filter complexity until you've used the simple stuff for a while. Most real-world queries are simple. The complexity comes from edge cases that are usually better solved with code.