JSONPath Tester

JSONPath Tester

Test JSONPath expressions with live results. Query JSON documents with JSONPath syntax. Free online JSONPath evaluator and debugger

JSONPath is XPath for JSON — and like XPath, the dialect you assume is rarely the dialect the library actually implements. RFC 9535 standardized the syntax in 2024, but Jayway, Goessner, JMESPath, and jq all predate the RFC and diverge in small but breaking ways. This tester runs your expression with a spec-aligned evaluator, shows every matched node with its full path, and warns when you use a feature that is not portable.

Which dialect are you actually writing?

JSONPath has been "the thing Stefan Goessner posted on his blog in 2007" for fifteen years. Every library reimplemented it slightly differently. The most common dialects in 2026:

  • RFC 9535 (2024) — the standardized version. Filter expressions use ?(@.price < 10), function extensions like length() and count() are defined, and slice/wildcard semantics are pinned down.
  • Jayway (Java) — used by Spring Cloud Gateway. Adds @.length(), inline path functions, and a permissive null-handling that the RFC rejects.
  • Goessner — the original. ..foo descendant scan, [?(@.x>5)] filters. No function library.
  • JMESPath — different syntax entirely (people.[name, age]), more powerful, but mutually unintelligible with JSONPath.

If you wrote your expression for Postman tests (Goessner-like) and your backend evaluates it with Jayway, expect surprises in null handling, filter scope, and array indexing.

Working example

Input

JSON:
{
  "store": {
    "books": [
      { "title": "Sapiens",        "price": 12.99, "tags": ["history"] },
      { "title": "The Pragmatic Programmer", "price": 34.50, "tags": ["dev"] },
      { "title": "Refactoring",     "price": 29.99, "tags": ["dev","architecture"] }
    ]
  }
}

Expression: $.store.books[?(@.price < 30)].title

Output

[ "Sapiens", "Refactoring" ]

Resolved paths:
  $["store"]["books"][0]["title"]
  $["store"]["books"][2]["title"]

The filter ?(@.price < 30) is applied to each array element. @ refers to the current element being tested. Note the RFC requires filters inside [] — bare $.store.books..price < 30 is not valid JSONPath.

Cheat sheet for the operators that actually matter

  • $ — root of the document. Always the first character.
  • .foo or ["foo"] — child by name. Use bracket notation for names with dots or non-identifier characters.
  • ..foo — descendant scan. Finds every "foo" key at any depth. Cheap to write, expensive on large documents.
  • [0] / [-1] / [0,2,4] / [1:5] — index, negative index, list, slice. Slice semantics follow Python: start inclusive, end exclusive, step optional.
  • * — wildcard. $.books[*].title returns every title.
  • ?(<filter>) — filter expression. @ inside refers to the current element. Comparisons: ==, !=, <, <=, >, >=. Logical: && (some dialects use "and"), ||, !.
  • @.length (Jayway) or length(@) (RFC) — string/array length. The Jayway form is the source of most "works in Postman, breaks in our gateway" bugs.

When to reach for this tool

  • You inherited a Postman test suite full of pm.expect(jsonPath(...)) and need to verify each assertion still matches against new API responses.
  • You are configuring an API gateway rule (Kong, Tyk, Spring Cloud Gateway) that selects fields by JSONPath and want to confirm it hits the right nodes before deploying.
  • You are debugging Datadog Logs or Splunk extraction rules that use JSONPath to pull fields out of structured log lines.
  • You are writing a transformer (jq, JSONata, JSONPath) and want to compare the same query in two syntaxes to see which expresses your intent best.

What this tool will not do

  • It will not execute JSONPath against streaming JSON. Filters require materializing the whole document — for streaming, use Jackson Streaming or ijson with manual path tracking.
  • It will not transform the document. JSONPath is a selection language, not a mutation language. For "select then update" use a jq pipeline or JSON Patch (RFC 6902).
  • It will not silently translate between Jayway and RFC 9535 dialects. If your expression uses @.length, it works one way; if you use length(@), it works the other. Pick a dialect and stick with it.

Your JSON is parsed and queried in this browser tab using a JavaScript implementation. API responses containing PII or tokens stay local.

Frequently asked questions

What is the difference between JSONPath and jq?

jq is a full programming language with pipes, functions, and assignment; JSONPath is a selection-only DSL. jq can transform documents and produce arbitrary output; JSONPath returns a list of matching nodes. For one-shot extraction either works; for any non-trivial transformation, jq is the right tool.

Why does $.store..price return numbers but $.store.books..price returns an array of numbers?

Both return an array — JSONPath always returns a (possibly empty) list of matches. Tools that pretty-print "12.99" instead of "[12.99]" are unwrapping single-element results for display. Treat the result as a list in your code and you avoid the off-by-one surprise when a query suddenly matches two things.

How do I filter on a field that has a hyphen in its name?

Use bracket notation with quotes: $.items[?(@["user-id"] == "abc")]. Dot notation is reserved for identifier-safe names.

Can I do regex matching in a filter?

In RFC 9535, yes: ?match(@.email, ".*@example\.com"). In Goessner-style JSONPath, no — you have to fall back to == comparisons. Jayway supports its own =~ operator: ?(@.email =~ /.*@example\.com/).

Why does my $.foo[*] return nothing when foo is an object?

In RFC 9535 and most modern dialects, [*] matches both array elements and object values. In stricter dialects it only matches arrays. If your foo is an object and the wildcard fails, your evaluator is using the strict semantics — use $.foo.* or rewrite as $..foo.*.

Is JSONPath safe to run on untrusted input?

The expression itself is safe to evaluate. The document being queried can be arbitrarily nested and large; on a small-stack runtime, $..* can blow the stack on hostile JSON. Either bound depth in your parser or accept JSON of bounded size at your API edge.

Related tools

Last updated · E-Utils editorial team