JSONPath is a query language for JSON, the JSON equivalent of XPath for XML. It lets you select nodes from a JSON document with a compact path expression. After years of incompatible implementations, IETF RFC 9535 (2024) standardised the syntax. This reference lists the standard segments and selectors with examples.
How it works
Every JSONPath starts at the root identifier $. You then chain segments that
each narrow or expand the set of selected nodes: .name or ['name'] for a
child, [*] for every element, .. to descend into all descendants, [0] or
[1:3] for array indices and slices, and ?<expr> to filter. Inside a filter,
@ refers to the item currently being tested. The engine evaluates the path
left to right, producing a node list.
Worked example
Given this document:
{ "store": { "books": [
{ "title": "A", "price": 8 },
{ "title": "B", "price": 15 }
] } }
$.store.books[*].titleselects["A", "B"]$..priceselects[8, 15](recursive descent)$.store.books[[email protected] < 10].titleselects["A"](filter)
Segment quick reference
| Syntax | Name | What it selects |
|---|---|---|
$ | Root | The entire document |
.name | Child (dot) | Field named name on current node |
['name'] | Child (bracket) | Same as dot notation; required when name has spaces or special chars |
[*] | Wildcard | Every child of the current node |
..name | Recursive descent | name anywhere in the subtree |
[2] | Index | Third array element (0-based) |
[-1] | Negative index | Last array element |
[1:4] | Slice | Elements at indices 1, 2, 3 (half-open, Python-style) |
[::2] | Slice with step | Every second element |
[::-1] | Reverse slice | All elements in reverse order |
[email protected] | Filter (existence) | Items that have an active field |
[email protected] > 5 | Filter (comparison) | Items where count is greater than 5 |
?length(@.tags) > 0 | Filter (function) | Items whose tags array is non-empty |
Array slicing in depth
RFC 9535 slices use Python semantics: [start:end:step] where the interval is half-open (start included, end excluded). Negative values count from the end, so [-3:] selects the last three elements. A step of -1 reverses the array. The defaults are start=0, end=length, step=1 when omitted.
For example, on an array of six elements [a, b, c, d, e, f]:
[1:4]→[b, c, d][-2:]→[e, f][::2]→[a, c, e]
Filter expressions and comparison rules
Filters keep only the nodes for which the expression evaluates to true. Type coercion does not happen: comparing a string to a number always returns false rather than coercing either side, which prevents unexpected matches. Logical operators && and || combine conditions, and existence tests ([email protected]) pass only if the field exists, regardless of its value.
Function extensions available in RFC 9535 include length() (string character count or array size), count() (number of nodes in a nodelist), match() and search() (regex matching), and value() (extracts the value from a single-node nodelist for comparison).
Notes and tips
Recursive descent .. is convenient but scans the whole subtree, so prefer an
explicit path on large documents. Filter comparisons are type-aware: comparing a
string to a number is simply false rather than an error. Use length() and
count() function extensions for size-based filters, e.g.
$.items[?length(@.tags) > 0].
Bracket notation ['field'] and dot notation .field are equivalent for simple identifiers, but bracket notation is required whenever the key contains spaces, hyphens, or starts with a digit. When migrating from older JSONPath libraries (such as Goessner 2007 or Jayway), note that RFC 9535 forbids several informal extensions — in particular, the ?(expr) filter shorthand that some libraries supported is now written ?expr inside the standard brackets.