JSONPath Tester_
JSONPath is not one language. It began as a blog post in 2007, was never specified, and was implemented independently dozens of times — which is why the question people actually ask is qualified by their stack. RFC 9535 finally standardised it in February 2024, seventeen years later, by which point Jayway, jsonpath-plus, jsonpath-ng, kubectl and Postgres had each settled on something slightly different.
This evaluates RFC 9535 and says where your expression depends on something the implementations disagree about. Every result also carries its normalized path — the canonical form that says exactly where the value came from.
- Input
- A JSONPath expression and a JSON document. Both are editable and the results update as you type.
- Output
- Every match with its value and its normalized path, plus a note wherever the expression relies on syntax that varies between implementations.
- Processing
- Parsed and evaluated in this tab against RFC 9535 — descendant ordering, no type coercion in comparisons, and functions rather than properties.
- Limits
- It answers for RFC 9535. That is the point of the portability notes: the library that will run this in production may well answer differently, and the notes say where.
- A query always returns a list
- Even $.store.book[0].title, which selects exactly one thing, produces a list of one. Every implementation does this and it is the commonest source of bugs in code that consumes the result — a value that is usually a single-element array and occasionally empty, handled as though it were the value itself.
Seventeen years without a specification
Why your library answers differently
Stefan Goessner published the idea in 2007 as an article with a reference implementation. It was widely adopted and never standardised, so every port made its own decisions about the parts the article left open — and there were many. RFC 9535 arrived in February 2024. Libraries written before it were not wrong; there was nothing to be wrong about. The practical consequence is that an expression verified in an online evaluator can behave differently in the service that runs it, and the difference is usually silent rather than an error.
The disagreements that actually bite
.length is the sharpest: under RFC 9535 it is an ordinary member name, so on an array it selects nothing at all, while jsonpath-plus and others return the element count. Descendant ordering was never specified, so $..x returns results in a different order depending on the library — and taking the first one is therefore a coin toss. Counting from the end with [-1], a step in a slice, and selecting several names in one bracket are all standard now and all missing from some implementations. Filters were the wildest part: several older libraries handed the expression to eval, which made ?(...) a small JavaScript program rather than a query.
A result is a list, and it is not the value
A query returns a nodelist — zero or more matches — and this is true even for an expression that obviously names one thing. Code that does result[0] works right up until the query matches nothing and the index returns undefined, or matches twice because a document changed shape. RFC 9535 also defines a singular query: an expression built only from names and indices, with no wildcards, filters or descendants. Those are guaranteed to select at most one node, and building the extraction path from a normalized path gives you one by construction.
The normalized path is the useful output
Every node in a document has one canonical JSONPath — single-quoted names in brackets, bare indices, nothing else: $['store']['book'][0]['title']. It is defined in RFC 9535 §2.7 and it is what a tester should be handing you, because knowing that a filter matched is half an answer and knowing which element it matched is the other half. Each path shown here is itself a valid expression that selects exactly that node, so the way to turn an exploratory query into production code is to run the query, find the node you want, and take its path.
Comparison does not coerce
Under RFC 9535, @.price == "10" does not match a numeric 10, and @.price < "20" matches nothing at all, because ordering compares two numbers or two strings and refuses anything else. A path that selects nothing is not null either — it is nothing, and every comparison against it is false, including == null. Testing whether a member is missing means testing for its presence and negating, not comparing it to null.
When something else is the better tool
JSON Pointer (RFC 6901) identifies exactly one location and cannot search — which makes it the right thing for a reference stored in a document, where ambiguity would be a bug. jq is a language with pipes, arithmetic and its own control flow, and it transforms as well as selects. JMESPath is a separate specification with its own grammar, used through the AWS tooling. JSONPath sits between them: more expressive than a pointer, far smaller than jq, and now — finally — specified.
Write it, check it, take the path
- 01Paste your JSON, or press Sample JSON for the bookstore document every implementation tests against.
- 02Type an expression, or start from one of the examples above the results.
- 03Read the count first. A query that matches nothing is valid and silent — the difference between zero matches and a syntax error is the difference between a wrong path and a typo.
- 04Take the normalized path of the node you want. It is a valid expression that selects exactly that node, which is what production code should use.
- 05Check the portability notes before shipping the expression. They say where RFC 9535 and the library you are using are likely to part company.
An expression that works here and not in production
The evaluator returns the array length and the Java service returns nothing. Neither is broken — they implement different things.
$.store.book.length
Nothing. .length is a member name. Use length($.store.book) if you mean the count.
Code that breaks on the second document
The extraction takes result[0] and the first document always matched once. The second matches twice, or not at all.
value = result[0]
A nodelist. Use a singular query — names and indices only — or handle zero and many explicitly.
Building an extraction path from an exploration
A filter found the record you want. Production should not re-run the filter, it should go straight to the node.
$..book[?@.isbn == '0-553-21311-3']
$['store']['book'][2] One node, no search, no dependence on filter semantics.
A filter that quietly matches nothing
The price is a number in the document and a string in the expression. RFC 9535 does not coerce, so the comparison is simply false.
$..book[?@.price < "10"]
$..book[?@.price < 10] Ordering compares two numbers or two strings, and nothing else.
The syntax, and where it is not portable
| Expression | What it selects | Portability |
|---|---|---|
$.a.b or $['a']['b'] | A named member. The bracket form is the normalized one | Universal |
$.a[0] | An array element by index | Universal |
$.a[-1] | Counting from the end | RFC 9535. Missing from several older libraries |
$.a[1:3] | A slice, ending before the second index | Widely supported |
$.a[::2] | A slice with a step | Patchy. A negative step is rarer still |
$.a[*] or $.a.* | Every child | Universal |
$..a | Every a at any depth | Order varies — it was never specified before 2024 |
$[0,2] or $['a','b'] | Several selections at once | RFC 9535. Partial elsewhere, especially for names |
$..a[?@.x > 1] | A filter | RFC 9535 drops the parentheses the 2007 form had |
?@.x | Existence — the nodes that have an x at all | Widely supported |
?length(@.x) > 3 | A function over a value | RFC 9535 only. Older libraries use properties |
Anything marked RFC 9535 was settled in February 2024. Before that there was no document to be right about, which is why libraries written earlier differ from each other rather than from a standard.
Working with JSONPath
- Name the implementation before the expression. "Does this JSONPath work" has no answer on its own — Jayway, jsonpath-plus, jsonpath-ng and kubectl are four different questions.
- Prefer a singular query in production. Names and indices only, no wildcard, filter or descendant — it selects at most one node by construction, which removes the zero-or-many handling entirely.
- Do not depend on the order of a .. result. It was unspecified for seventeen years, and code that takes the first match is relying on a library detail rather than on the query.
- Use length() rather than .length. They mean different things under RFC 9535, and the difference is silent.
- Match the types in a filter. A number in the document needs a number in the expression, because no implementation that follows the specification will coerce them.
- Reach for JSON Pointer when you want exactly one location. Its inability to search is the feature — a reference that could match twice is a bug waiting to happen.
Where JSONPath goes wrong
The same expression means different things in different libraries
This is not a matter of bugs. There was no specification until 2024, so implementations diverged on descendant ordering, unions, negative indices, functions and filter semantics. An expression verified in one place is not verified anywhere else.
A result is a list even when it looks like a value
Code that indexes straight into it works until the query matches nothing, and then reads undefined and passes it on. Zero matches is not an error condition in JSONPath — it is an ordinary answer.
.length is a member name, not a count
RFC 9535 treats it as an ordinary key, so on an array it selects nothing. Several popular libraries special-case it and return the length, which makes it one of the few constructs that produces a confidently wrong answer rather than an error.
Filters were once arbitrary JavaScript
Older implementations evaluated the filter expression with eval, which made ?(...) a program rather than a query and a security problem if the expression came from anywhere untrusted. RFC 9535 defines a closed grammar instead.
A missing member is not null
A path that selects nothing has no value, and every comparison against it is false — including == null. Test for absence by negating an existence test, not by comparing.
Specification, semantics and scope
- Standard
- RFC 9535, February 2024. Before it, the reference was Stefan Goessner's 2007 article, which was never a specification and left much of the syntax to the implementer.
- Result
- A nodelist — zero or more nodes, each with a value and a location. A singular query, built only from name and index selectors, is guaranteed to select at most one.
- Normalized paths
- RFC 9535 §2.7. Names single-quoted inside brackets, indices bare and non-negative, no shorthand. Every path shown here is a valid expression selecting exactly its node.
- Descendants
- RFC 9535 §2.5.2.2 fixes the order: each node is visited before its descendants and the selector is applied to each in turn. Earlier implementations chose their own order.
- Comparison
- No type coercion. Equality compares values structurally; ordering applies only between two numbers or two strings, and anything else is simply false rather than an error.
- Functions
- length(), count(), match(), search() and value() are defined by the RFC. This page implements length() and count(), which cover almost all real filters.
- Network
- None from tool code. A test sweep calls every function this page uses with
fetchandXMLHttpRequestreplaced by stubs that throw, so a stray request fails the build instead of shipping. Disconnect from the network and the page still works.
Questions about JSONPath
Is JSONPath standardised?
Since February 2024, yes — RFC 9535. Before that it was a 2007 article with a reference implementation and no specification, which is why libraries written earlier differ from one another on descendant ordering, unions, negative indices, functions and filter semantics.
Why does my JSONPath expression work differently in Java and JavaScript?
Because Jayway and jsonpath-plus were both written before there was anything to conform to, and they made different choices. The differences are mostly silent — a different result rather than an error — which is why the notes on this page name the specific constructs rather than claiming an expression is portable.
Why does a JSONPath query return an array for a single value?
Because the result of a query is a nodelist, always. Even $.a.b.c produces a list of one, and a query that matches nothing produces an empty list rather than an error. RFC 9535 calls an expression built only from names and indices a singular query — it selects at most one node, which is what production extraction should use.
How do I get the length of an array in JSONPath?
With the length() function, in an implementation that has it — length($.items). The .length property form works in jsonpath-plus and some others, and under RFC 9535 it is an ordinary member name, so on an array it selects nothing at all. This is the sharpest disagreement in the format.
What is a normalized path?
The canonical JSONPath for one node: single-quoted names in brackets, bare indices, no shorthand — $['store']['book'][0]. Defined in RFC 9535 §2.7. It is a singular query by construction, so it is the right thing to lift out of an exploratory session and put into code.
Do filter comparisons convert types?
Not under RFC 9535. @.price == "10" does not match a numeric 10, and ordering comparisons only apply between two numbers or two strings — anything else is false. Some older implementations did coerce, which is another silent difference.
What is the difference between JSONPath and JSON Pointer?
JSON Pointer (RFC 6901) identifies exactly one location and has no wildcards, filters or search. That limitation is its purpose: a pointer stored in a document cannot become ambiguous later. JSONPath is a query language and can match many nodes or none.
What is the difference between JSONPath and jq?
jq is a full language — pipes, arithmetic, conditionals, user-defined functions — and it transforms data as well as selecting it. JSONPath only selects. If you are reshaping output rather than extracting a value, jq is the tool, and JMESPath sits somewhere between the two.
Why does $..x return results in a strange order?
Because the order was unspecified until 2024 and implementations chose differently. RFC 9535 now defines it — each node is visited before its descendants, and the selector applies to each in turn — but code that relies on the first result of a descendant query is depending on a library detail rather than on the query itself.
Is the JSON I paste here uploaded?
No. The work is JavaScript running in this tab. Every function it calls is covered by a test that stubs fetch and XMLHttpRequest to throw, so a request that slipped in would break the build rather than reach a server — and you can confirm it for yourself by disconnecting and carrying on.