JSONPath is a small query language for pointing at values inside a JSON document, in the same way XPath points at nodes in XML. It is used in API testing tools, log processors, Kubernetes (kubectl -o jsonpath), and in assertions such as "check that $.data.items[0].status equals active". The syntax was standardized in 2024 as RFC 9535, though older tools still have small dialect differences.
All examples below run against this document:
json{ "user": { "id": 42, "name": "Asha", "emails": [ "test@example.com", "hello@example.com" ], "address": { "city": "Austin", "zip": "78701" } }, "orders": [ { "id": "o1", "total": 10.5 }, { "id": "o2", "total": 25.0 } ] }
| Syntax | Meaning |
|---|---|
$ | The root of the document |
.name or ['name'] | A child key (use brackets for keys with spaces or dashes) |
[0], [-1] | An array item by index; negative counts from the end |
[*] or .* | Every item or every value (wildcard) |
..name | Recursive descent: name at any depth |
[0:2] | An array slice (items 0 and 1) |
[?(...)] | A filter that keeps items matching a condition |
$.user.name returns "Asha".$.user.address.city returns "Austin".$.user.emails[0] returns "test@example.com".$.user.emails[-1] returns the last email, "hello@example.com".$['user']['address']['zip'] is bracket notation for "78701".$.orders[*].id returns every order ID: ["o1", "o2"].$.orders[0:1] returns a list containing only the first order.$.orders.length is not standard JSONPath. Some tools support it, but RFC 9535 does not. If you need a count, take the length of the result array in your code.Filters select array items whose fields match a condition. Inside a filter, @ refers to the current item:
$.orders[?(@.total > 20)] returns the order with total 25.0.$.orders[?(@.id == 'o1')].total returns [10.5].$.orders[?(@.discount)] returns orders that have a discount key (none here).Filter syntax differs most between tools. RFC 9535 writes filters as [?@.total > 20] without parentheses, while many older libraries require [?(@.total > 20)]. If a filter returns nothing, check which form your tool expects.
$..id returns every id at any depth: [42, "o1", "o2"].$..city finds "Austin" without needing to know the full path.Recursive descent is convenient for exploring, but it can match more than you expect in large documents. Use explicit paths in tests and production code.
You don't have to write paths by hand. In the JSON Formatter, click JSONPath in the editor toolbar. The panel lists the path of every value in the document next to its value. Search by key or value, then copy the exact path you need into a test, a jq filter or your code.
jq has its own syntax, which is similar but not identical:
| Task | JSONPath | jq |
|---|---|---|
| A nested value | $.user.address.city | .user.address.city |
| All order IDs | $.orders[*].id | .orders[].id |
| Filter | $.orders[?(@.total > 20)] | .orders[] | select(.total > 20) |
$.orders, then $.orders[0], then $.orders[0].total.$.User and $.user are different paths.