The syntax this page accepts
JSONPath has no single normative grammar that every library follows, so implementations disagree at the edges. What is supported here is the common core, plus the filter form that people actually reach for:
| Written | Selects |
|---|---|
$ | The whole document |
$.store.name | A named property, one level at a time |
$['odd key'] | The same, when the key has spaces or punctuation |
$.orders[0], $.orders[-1] | Array element by index; negative counts from the end |
$.orders[*], $.* | Every element of an array, or every value of an object |
$.orders[0:5], $.orders[-3:] | Slices, with an optional third step value |
$.orders[0,2,4] | A union of specific indexes |
$..sku | Every sku property at any depth |
$..* | Every node in the document |
$.orders[?(@.status == "open")] | Array elements passing a comparison |
$.orders[?(@.discount)] | Array elements where the property exists at all |
Not supported, deliberately: script expressions, arithmetic inside a filter, regular expression matching, and the && and || operators. Those are the parts where libraries diverge most, and the script-expression form is the one that has historically been implemented by handing user text to an evaluator. Chain two runs, or filter once and read the rest by eye.
Why nothing is evaluated
The original JSONPath article described [?(...)] as an expression evaluated by the host language, and plenty of implementations did precisely that. On a web page, running pasted text through an evaluator is a straightforward way to turn a formatting tool into a place where arbitrary code runs against whatever else is in the tab. This page parses the expression into a small list of steps — child, descendant, index, slice, filter — and walks them over the parsed document. There is no interpreter to reach, which is also why the unsupported syntax above is genuinely unsupported rather than quietly handled.
Reading a result that is empty
An empty result is the normal outcome of a nearly correct path, and it is worth having a routine for. Delete the last step and rerun. Keep deleting until something matches, and the first step you removed is the one that failed. From there the causes are few: a case difference in the key, a property that only exists on some elements, a step that treats an array as an object, or a filter comparing a number to a string. That last one is quiet and common — a JSON value of "12" will never satisfy @.qty > 10 here, because a string and a number are not compared numerically.
Recursive descent is the blunt instrument for this. $..status finds every status anywhere in the document and prints the full path to each one, which tells you where the thing you were looking for actually lives. Then write the precise path.
Paths are output, not just input
Every match is listed with the path that reached it, in normalised form: bracket notation for anything that is not a plain identifier, numeric indexes for array positions. That output is usually more useful than the value. It is the string you paste into a config file that expects a single path, the thing you quote in a bug report so a colleague can find the same node, and the way you discover that the field you wanted is at $.data.attributes.items[3].sku rather than the two-level path you assumed.
Where you are likely to be using this
Path expressions turn up in API gateway response mappings, log pipeline field extraction, contract tests that assert on one field of a response, monitoring checks that pull a number out of a health endpoint, and the query box of half the tools that consume JSON. In every one of those places the expression is written blind against a document nobody has fully read. Getting it right here first, against a real captured response, saves a deploy cycle. Once the shape is settled, the JSON to TypeScript page will turn the same document into types, and the JSON diff will tell you when the shape changes underneath you.
Limits worth knowing before you trust the count
Results are capped at twenty thousand nodes, which only $..* on a large document will reach. Numbers come out of JSON.parse, so integers beyond about nine quadrillion have already lost their low digits before any matching happens, and a filter comparing against one of those is comparing against a rounded value. Object key order in the output follows insertion order as the browser reports it, which is stable in practice but is not something JSON itself promises. None of that affects ordinary use; all of it affects the one time you are chasing something strange.
Questions people ask
Which JSONPath dialect is this?
The common core that nearly every implementation agrees on: root, child, recursive descent, wildcards, indexes, slices, unions and a single-comparison filter. There is no one specification that all libraries follow, and they differ most on filters, on whether a union may mix names and indexes, and on the exact result of a slice with a negative step. If your target runtime is a specific library, treat a result here as a strong hint rather than a guarantee and confirm the final expression against that library.
Why does my filter with && not work?
Compound filters are not supported. Combining conditions is one of the least consistent parts of JSONPath across implementations, and supporting it well means building a real expression grammar. Run the first condition, look at the paths that come back, and apply the second condition by eye or as a second pass on a narrowed document. If you need compound filtering in production, the library you deploy against will have its own rules, and those are the ones that matter.
Can a path I paste run code?
No. The expression is parsed into a list of steps and walked over the document. Nothing is passed to eval or to the Function constructor, and there is no place where text from either input box is treated as code. That is also the reason script expressions of the [(@.length-1)] form are rejected instead of being supported.
Does .. on an array find things inside it?
Yes. Recursive descent walks into both objects and arrays, so $..sku will find sku properties inside objects nested in arrays nested in objects, at any depth. It reports each match with the full path, including the array indexes it passed through, which is usually how you find out the structure was one level deeper than you thought.
Is the document sent anywhere?
No. Parsing, matching and rendering all happen in this tab, with no request made and nothing stored. Captured API responses are the usual input here and they frequently contain tokens, session identifiers and personal data, so it is worth being explicit: it is safe to paste, but the content is on your screen, and anything you copy out of the results box carries the same content with it.