JSONPath is a query language for JSON, providing the same kind of document-navigation power that XPath gives XML developers. Instead of writing nested loops or chained property accesses, you write a compact expression that pinpoints exactly the data you need — whether it lives three levels deep or is scattered across a hundred-element array.
What Is JSONPath and Why Does It Matter?
Stefan Goessner introduced JSONPath in 2007 as a direct analog to XPath 1.0 for XML. Just as XPath lets you write //book[@price<10] to pluck cheap books out of an XML document tree, JSONPath lets you write $.store.book[?(@.price < 10)] to do the same against a JSON structure.
The practical value is highest when working with API responses you don't fully control. Rather than parsing the entire payload and walking the object graph yourself, you hand a JSONPath expression to a library and get back exactly the nodes you care about. This approach is used by REST-testing frameworks like REST Assured in Java, by AWS's query language JMESPath in the AWS CLI, and by the widely used jq command-line tool (which has its own syntax but borrows heavily from the same ideas).
The Root Operator and Basic Navigation
Every JSONPath expression starts with $, representing the root of the document. From there you navigate using dot notation or bracket notation.
$.store.book.title
This reads as: "start at root, enter store, then book, then title." Bracket notation is required when a key contains a hyphen, space, or starts with a digit:
$['store']['book']['title']
$['content-type']
Sample document used throughout this article:
{
"store": {
"book": [
{ "title": "Sayings of the Century", "author": "Nigel Rees", "price": 8.95 },
{ "title": "Sword of Honour", "author": "Evelyn Waugh", "price": 12.99 },
{ "title": "Moby Dick", "author": "Herman Melville","price": 8.99 },
{ "title": "The Lord of the Rings", "author": "J.R.R. Tolkien", "price": 22.99 }
],
"bicycle": { "color": "red", "price": 19.95 }
}
}
$.store.bicycle.color returns "red". $.store.book[0].title returns "Sayings of the Century".
Wildcards and Recursive Descent
The wildcard * matches all properties or elements at the current level:
$.store.book[*].author
// ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J.R.R. Tolkien"]
The recursive descent operator .. searches at any depth, not just the current level:
$..author
This finds every author field anywhere in the document tree, regardless of nesting depth — invaluable when you don't know the exact structure of a deeply nested API response but do know the key name. It can also be combined with a wildcard or index, e.g. $..[0] returns the first element of every array found anywhere in the document.
Array Slicing and Index Access
JSONPath supports Python-style array slicing:
$.store.book[0] /* first book */
$.store.book[-1] /* last book (negative index) */
$.store.book[0,2] /* first and third books (union) */
$.store.book[0:2] /* first two books (slice, exclusive end) */
$.store.book[1:3] /* second and third books */
$.store.book[*] /* all books */
The slice [start:end] follows Python convention — start is inclusive, end is exclusive. Negative indices count from the end, so [-1] always gives the last element regardless of array length — cleaner than computing array.length - 1 in application code.
Filter Expressions
Filter expressions select array elements matching a condition, using [?( expression )] where @ refers to the current element:
/* books costing less than $10 */
$.store.book[?(@.price < 10)]
/* books that have an isbn property */
$.store.book[?(@.isbn)]
/* books priced between $8 and $13 */
$.store.book[?(@.price >= 8 && @.price <= 13)]
Filters are evaluated per element — @ is bound to each array element in turn, and only elements where the expression is true are included. The first example above returns the two books priced at $8.95 and $8.99.
A more real-world example — extracting users whose email domain is gmail.com:
$.users[?(@.email =~ /.*@gmail\.com$/)]
Note that regex support (=~) is available in some implementations (notably the Jayway Java library) but isn't part of the original Goessner spec — check your library's docs for which operators are supported.
Built-in Functions
RFC 9535 (the JSONPath 2.0 spec, published 2024) formalizes a set of built-in functions:
$.store.book.length() /* number of books */
$.store.keys() /* all keys of the store object */
$.store.book[*].price.min() /* minimum price across all books */
$.store.book[*].price.max() /* maximum price */
$.users[?( match(@.name, "A.*") )] /* string matching */
length() is especially useful inside filters: $.users[?( length(@.tags) > 2 )] returns only users with more than two tags. RFC 9535's match() and search() functions use I-Regexp patterns — match() requires a full-string match, search() succeeds if the pattern matches anywhere in the string.
Practical Examples
Extract all email addresses from a user list:
{
"users": [
{ "id": 1, "name": "Alice", "email": "alice@example.com" },
{ "id": 2, "name": "Bob", "email": "bob@example.com" },
{ "id": 3, "name": "Carol", "email": "carol@example.com" }
]
}
$.users[*].email
// ["alice@example.com", "bob@example.com", "carol@example.com"]
Find all books under $15:
$.store.book[?(@.price < 15)]
// Returns the three objects priced 8.95, 12.99, and 8.99
Get the last element when array length is unknown:
$.store.book[-1]
// Returns the Lord of the Rings object
Where JSONPath Is Used in Practice
REST Assured (Java) ships with built-in JSONPath support for API test assertions:
given().when().get("/books").then().body("store.book[0].title", equalTo("Sayings of the Century"));
JMESPath is AWS's answer to JSONPath, used by the AWS CLI's --query flag. The syntax differs (no $ root), but the concept is identical:
aws ec2 describe-instances --query "Reservations[*].Instances[?State.Name=='running'].InstanceId"
jq is the de facto standard for JSON processing on the command line. It has its own richer syntax, but many core ideas — .foo for property access, .[] for iteration, select() for filtering — map directly onto JSONPath concepts. Once you know JSONPath, picking up jq is straightforward.
JSONPath vs. JavaScript Dot-Notation Chaining
In JavaScript, plain property access (data.store.book[0].title) works fine for deterministic paths, but has real limits: if any intermediate property is undefined, the expression throws a TypeError. You need optional chaining (data?.store?.book?.[0]?.title) to be safe, and there's no built-in way to express "all elements" or "any depth."
JSONPath handles both cases with a single expression, and returns an array of matches (zero, one, or many) — which maps naturally onto scenarios expecting multiple results. For simple, known-path access, plain JS dot notation is fine. For flexible querying, filtering, and deep searches across variable-shaped data, a proper JSONPath library is the better tool.
Summary
The core operators to know: $ (root), . and [] (navigation), * (wildcard), .. (recursive descent), array slices ([0] / [-1] / [0:3]), and filter expressions ([?(@.price < 10)]). These seven cover the vast majority of real-world data-extraction tasks — for testing APIs, querying cloud resources, or processing large JSON payloads on the command line.
Want to test JSONPath expressions on your own data? JSON Formatter Hub lets you paste JSON and explore the structure interactively, entirely in your browser.
Top comments (0)