Skip to main content

JSONPath Evaluator

Query JSON the way you would in code.

Write a JSONPath expression, run it, and see every match with its resolved path and value. Click a node in the tree to have its path written for you, then generalise it into a query.

Valid JSONCalculating statistics…

Input JSON

Size: 472 BLines: 27
0 matches

No values matched $.projects[*].name.

What JSONPath is for

JSONPath is a query language for JSON, in roughly the way XPath is one for XML. You write an expression describing where a value lives, and you get back every value that matches — one, none, or several hundred.

It is worth learning because it turns up everywhere once you notice it: Kubernetes kubectl -o jsonpath, CI pipeline conditions, API gateway response mapping, log processors, Postman assertions and countless integration tools. The same expression you test here usually pastes straight into one of them.

The syntax, with real results

Every result below is what this page returns for a document with three projects under $.store.projects, two of them active.

ExpressionReturns
$The whole document.
$.store.projectsOne value — the array itself.
$.store.projects[0]The first item.
$.store.projects[-1:]The last item. Slices count from the end with a negative start.
$.store.projects[:2]The first two items.
$.store.projects[0,2]Items 0 and 2 — a union, not a range.
$.store.projects[*].name["api", "web", "cli"] — one field from every item.
$..nameEvery name at any depth, wherever it lives.
$..*Every node in the document. Useful for counting, rarely for reading.
$.store.projects.length3. Convenient, and not portable — see the dialect note below.

Bracket notation is always available and is what you need for keys that are not identifiers: $['shipping-address'].city. The viewer writes paths in that form automatically when it has to.

Filters

Filters are where JSONPath stops being a lookup and starts being a query. ?() takes an expression evaluated against each item, with @ standing for the item.

$.store.projects[?(@.status=="active")].name    ["api", "cli"]
$.store.projects[?(@.stars > 10)].name          ["api", "cli"]
$.store.projects[?(@.owner)].name               ["api", "web"]
$.store.projects[?(@.name.match(/^a/))]         the "api" object
$..[?(@.email)]                                 every object anywhere with an email

The third form — a bare field name — tests for existence, which is the fastest way to find the records that are missing something. The fourth shows that string methods work inside a filter; .match() with a regular expression is the one worth remembering.

What a match actually gives you

Each result carries four things, not just the value:

{
  "path": "$['store']['projects'][1]['name']",
  "pointer": "/store/projects/1/name",
  "parentProperty": "name",
  "value": "web"
}

The path is normalised to bracket form — it will not look like the expression you typed, and that is deliberate: it is unambiguous and always valid. The pointer is an RFC 6901 JSON Pointer, which is what JSON Patch, OpenAPI and many schema validators expect. Getting both means you can query in one notation and act in the other.

From a tree click to a query

The practical workflow is not to write expressions from memory. It is to find one example of what you want, copy its path, and widen it.

  1. Find any single matching value in the tree view and right-click → Copy JSON Path.
  2. Paste it here. You now have an expression that returns exactly one value, and you know it is correct.
  3. Replace a concrete index with * to get every sibling: $.users[3].email becomes $.users[*].email.
  4. Replace the leading path with .. if the field appears at several depths: $..email.
  5. Add a filter to narrow it back down: $.users[?(@.active)].email.

Each step is verifiable against the real document, so you never have to guess whether an expression works — you can see how many matches it returned.

Filters cannot run code

Historically, JSONPath filter expressions were evaluated with eval, which meant a query was arbitrary JavaScript. This page runs a restricted evaluator instead: expressions can read the data and compare it, and nothing else. All of the following are rejected outright rather than executed.

AttemptedResult
$.a[?(@.x=1)]Rejected — assignment is not an expression here.
$.a[?(globalThis)]Rejected — globals do not resolve.
$.a[?(process)]Rejected.
$.a[?(@.x.constructor)]Rejected — no constructor access.
$.a[?(Object.keys(@))]Rejected — no built-ins.
$.a[?((()=>1)())]Rejected — no function construction.
$.a[?(@.x > 1)]Allowed — reading and comparing your own data.

A note on dialects

JSONPath went unstandardised for two decades, so implementations diverge. It was finally specified as RFC 9535 in 2024, and most existing tools — including the widely used jsonpath-plus, which powers this page — predate it and carry extensions.

  • $..name recursive descent, [*], slices and filters are universal. Use these freely.
  • .length, the parent operator ^ and the property-name operator ~ are extensions. They work here and will not work everywhere.
  • Regular expressions inside filters vary by implementation.
  • Behaviour on no match differs: some implementations return an empty list, others an error.

If the expression is destined for kubectl or another specific tool, test it there before relying on it. If it is for your own code, pick a library and stay with it. For everything else, the universal subset is enough surprisingly often.

Frequently asked questions

Which JSONPath dialect is used?
jsonpath-plus, in its restricted evaluation mode. It supports the common syntax plus extensions such as ^, ~ and .length. JSONPath was standardised as RFC 9535 only in 2024, so expressions using extensions may not port to other tools.
How do I get every value of one field?
Use a wildcard if you know where it lives — $.users[*].email — or recursive descent if you do not: $..email finds every email at any depth.
Can I filter by a value?
Yes. $.users[?(@.active==true)] compares, $.users[?(@.age > 30)] works on numbers, and $.users[?(@.email)] tests that a field simply exists.
Why does the returned path look different from my expression?
Results are normalised to bracket notation — $['users'][0]['email']. It is unambiguous and always valid, including for keys containing dots or hyphens. Each result also carries an RFC 6901 JSON Pointer.
Can a JSONPath expression run arbitrary code?
Not here. Filters run in a restricted evaluator: assignments, globals, constructors and function construction are all rejected. Only reading and comparing your own data is permitted.
Are queries run on a server?
No. The expression is evaluated against the document in your browser. Neither the query nor the data leaves the page.
Why does my query return nothing?
Usually a path segment that does not exist, or an index on something that is not an array. Shorten the expression until it returns something, then add segments back one at a time — the match count after each step tells you where it broke.