JSONPath Tester
Write a query, see what it matches, before you put it in code
JSON document
Matches
Syntax worth remembering
- the root of the document
- a child property
- every author at any depth
- by index, from zero
- the last element
- several indexes at once
- a filter on each element
- elements where a field exists
- every direct child
- every value in the document
Querying a document instead of walking it
JSONPath is to JSON roughly what XPath is to XML: one expression that reaches into a nested document and pulls out the parts you want. $.subscribers[?(@.status === "active")].msisdn gives you every active subscriber's number without a loop, an index, or four levels of optional chaining.
The reason to test one in a browser rather than in your code is that JSONPath expressions fail quietly. An expression that selects nothing is not an error — it is an empty array, which looks exactly like "there were no active subscribers" when it actually means "you wrote subscriber instead of subscribers". Watching the match count change as you type is the fastest way to tell those two apart.
Evaluation here uses JSONPath-Plus, the most widely used JavaScript implementation. Worth knowing before you rely on an exotic expression: JSONPath spent twenty years as a 2007 blog post by Stefan Goessner with no formal specification, so implementations diverge at the edges — filters and script expressions especially. RFC 9535 standardised it in 2024, and libraries are still catching up. If an expression has to work in two languages, keep it to the basics and test it in both.
Your document stays in the browser. Nothing is uploaded.
How to use it
- Paste the document – An API response, a config file, a log entry — whatever you are trying to reach into.
- Type an expression – Start with $ and build outward. Results update as you type, so you can watch the match count respond to each segment you add.
- Turn on Show paths when a match surprises you – Values alone do not tell you where they came from. Paths do, and that is usually the thing you actually needed.
- Copy the expression into your code – Once the match count is what you expect, the expression is right — which is a much stronger position than finding out in production.
When an expression returns nothing, cut it back to $ and add one segment at a time. The segment where the count drops to zero is the one that is wrong, and it is almost always a plural — result against results, item against items. This takes about fifteen seconds and beats staring at the full expression trying to spot the typo.
Four expressions against one response
A network KPI response with cells nested under sites. Each expression below answers a question you would otherwise write a loop for, and the fourth one is the interesting case — a recursive descent that reaches every rsrp reading regardless of how deeply it is nested.
{
"sites": [
{ "siteId": "SITE-4471",
"cells": [
{ "cellId": "C-4471-A",
"rsrp": -88 },
{ "cellId": "C-4471-B",
"rsrp": -104 }
] },
{ "siteId": "SITE-4472", … }
]
}# every site id $.sites[*].siteId → "SITE-4471", "SITE-4472" # cells with weak signal $..cells[?(@.rsrp < -100)].cellId → "C-4471-B", "C-4472-B" # the last site $.sites[-1:].city → "Cardiff" # every rsrp, however deep $..rsrp → -88, -104, -71, -119
Where you would use it
Extracting a value in a CI pipeline or a test
Assertions against API responses are full of JSONPath, and so are Kubernetes commands — kubectl get pods -o jsonpath='{.items[*].metadata.name}' is the same idea, with its own dialect. Getting the expression right against a saved response first saves a lot of failed pipeline runs.
Working out the shape of an unfamiliar API
$..*~ style exploration is slow; a few recursive-descent queries are fast. $..id tells you every place the API calls something an id, which is usually more informative than the documentation about how the resources relate.
Following up a schema failure
The schema validator reports failures as JSON Pointers like /subscribers/2/msisdn. Turning that into $.subscribers[2].msisdn here shows you the offending value and its neighbours, which is normally what you need to understand why it is wrong.
Pulling one column out of a nested response
When you want a flat list from a nested document — every price, every email, every cell id — one recursive expression beats writing the traversal. Feed the result into JSON to Table if you would rather look at it as a grid.
What the page does
- Evaluates as you type, with a live match count — the number is the fastest signal that an expression is wrong.
- Distinguishes no matches from a broken expression. They are different problems and most tools show you the same empty box for both.
- Shows the path of every match on request, so two identical values are still tellable apart.
- A syntax reference on the page, because nobody remembers whether it is
[-1:]or[-1]for the last element. (It is[-1:].) - Runs entirely in your browser — the document is not sent anywhere.
Questions people ask
Why does my filter expression work here but not in Python?
Because filters are where implementations diverge most. JSONPath had no specification until RFC 9535 in 2024, so every library invented its own filter semantics — JSONPath-Plus evaluates a JavaScript expression, jsonpath-ng in Python does not, and the two disagree about operators, string comparison and truthiness. Property access, indexes, slices and recursive descent behave the same nearly everywhere; anything with ?() in it should be tested in the language you are actually shipping.
What is the difference between JSONPath and a JSON Pointer?
A JSON Pointer identifies exactly one location — /subscribers/2/msisdn, no wildcards, no filters. JSONPath is a query language that can match many locations at once. Pointers are what error messages and patch documents use, because they must be unambiguous; JSONPath is what you use when you want everything matching a condition.
Is this the same as jq?
They overlap but jq is a different and larger thing — a full transformation language with its own pipeline syntax, so it can reshape output as well as select it. JSONPath only selects. If you are writing a shell one-liner, jq is usually the better tool; if you are embedding an expression in a config file, a test assertion or a Kubernetes command, JSONPath is what those accept.
Does it handle very large documents?
Comfortably, up to a few megabytes. Everything runs in the browser tab, so the practical limit is the tab's memory rather than any restriction here. A recursive-descent expression like $..* on a large document is the one to be careful with — it visits every value, so it is quadratic-feeling on deeply nested data.
Why does $..price return prices from places I did not expect?
That is recursive descent doing exactly what it says: every price at any depth, including ones inside unrelated objects. It is the most useful operator in JSONPath and the easiest one to be too clever with. Turn on Show paths and you will see immediately which branch the unexpected values came from.
Related tools
Further reading
- RFC 9535 — JSONPath – The 2024 standard. Worth skimming the filter section if you have ever been bitten by two libraries disagreeing.
- kubectl JSONPath support – The dialect kubectl accepts, which is close to but not the same as this one.