JSONPath is a query language for reaching into a JSON document and pulling out the parts you want. If you have used XPath for XML, the idea is the same, and the syntax borrows a lot of its shape. You write an expression that describes a path from the top of the document to the values you are after, and the engine returns whatever matches.
Rather than list operators in the abstract, this page threads one document through every example. Here it is, a tiny store with a few books and a bicycle. Every query below runs against exactly this.
The root and how to descend
Every JSONPath expression starts at $, the root of the document. From there you step into keys and arrays. There are two ways to name a key, and they are interchangeable except at the edges.
Bracket notation earns its keep when a key is not a tidy identifier: keys with spaces, dots or hyphens have to go in brackets and quotes, as in $['first name']. Dot notation is shorter for everything else. Array elements use a numeric index in brackets, zero-based, so the first book’s title is a straight path down.
Wildcards
A wildcard *matches every element of an array or every value of an object. It is how you say “all of these” without naming each one. Both forms below fan out across the four books.
Point a wildcard at an object instead and it walks that object’s values. Against the store, this returns the book array and the bicycle object side by side.
Array slices
Slices borrow Python’s syntax: [start:end], where start is inclusive and end is exclusive. Leave a bound out to run to the edge, and use a negative index to count from the back.
Selecting several named keys at once
Bracket notation does one more thing dot notation cannot: it takes a comma-separated union of keys or indexes, pulling several specific elements in one expression. This is handy when you want two named fields, or a scattered set of array positions, without a wildcard sweeping up everything.
The same union works on object keys, so $.store['bicycle','book'] returns both branches of the store. Support for unions is one of the more consistent features across engines, though the exact output ordering can differ, which matters if you depend on the order.
Filters
Filters are where JSONPath gets genuinely useful. A filter expression ?(...) keeps only the elements for which the condition is true. Inside the filter, @ refers to the element currently being tested, so you compare its fields.
A filter can also test for the presence of a field. This next one keeps only books that carry an ISBN, which drops the first two.
Filters combine with the other operators. Add a trailing key to pull one field off each surviving element, so you get a clean list of titles rather than whole objects.
Recursive descent
The double dot ..searches at any depth. It is the tool for “find every X, wherever it lives,” without spelling out the path. Against the store, this collects every price in the document, from both the books and the bicycle.
Recursive descent pairs naturally with a wildcard or a filter. $..author gathers every author anywhere in the tree, and $..book[?(@.price > 20)] finds pricey books no matter how deeply the book array is nested. It is powerful, and on large documents it is also the slowest operator, since it visits every node, so prefer an explicit path when you know one.
Implementations vary
JSONPath started as a 2007 blog post, not a formal spec, so for years every library implemented it a little differently. That changed in 2024, when the IETF published RFC 9535, a real standard for JSONPath. Newer libraries are converging on it, but plenty of code in the wild still predates it.
The core, everything above through recursive descent, is portable. Divergence concentrates in the filter syntax and in a few edge behaviors:
- Filter comparison operators: some engines want ==, some accept a single =, and support for regex matching or string functions inside a filter is very uneven.
- Quoting inside filters: RFC 9535 uses single quotes for string literals; older engines mixed single and double, so a filter that works in one tool can fail to parse in another.
- Result shape: whether a single-match query returns a scalar or a one-element list differs, which trips up downstream code that assumed one or the other.
The practical rule: keep expressions simple, prefer == and single quotes in filters, and test against the exact library you will run in production. When you are just eyeballing structure to find the right path, a formatter helps first; our JSON formatter and JSON validator both run locally in the browser, and the JSON diff is handy when two responses should match but do not.
How to read a query, and how to build one
Reading a JSONPath expression is easiest left to right, as a series of steps down the tree. Take $.store.book[?(@.price < 10)].title: start at the root, step into store, then book, keep only the elements where the price is under ten, and from each survivor pull the title. Every operator narrows or fans out the set of nodes you are holding, and the final one decides what comes back.
Building one runs the same way in reverse. Decide what you want out, then walk backward to the root, asking at each level whether you know the exact key (a plain path), want all children (a wildcard), or want a subset (a filter or a slice). Writing the path in that order tends to produce something simpler than reaching for recursive descent by reflex, and simpler expressions are the ones that behave the same across libraries.
One behavior shapes a lot of downstream code: most engines return a listof matches, sometimes called a nodelist, even when only one node matches. Code that reads a single value out of a JSONPath result usually has to index into that list, and forgetting to is a frequent source of “why is my value wrapped in an array” confusion.
JSONPath versus jq
JSONPath and jq overlap but aim at different jobs. JSONPath is a selection language, best embedded in another tool or config to point at values. jq is a full transformation language with pipes, functions and its own logic, better on the command line when you need to reshape data as well as extract it. If you are querying a config file or a policy engine, you probably want JSONPath; if you are munging an API response in a terminal, jq usually wins.
For the regex-shaped parts of both, and the flavor gotchas that come with them, the cluster pillar, the regex cheat sheet, is the companion piece.