This is the applied companion to our JSONPath syntax guide: twelve real tasks you hit when working with JSON API responses, each paired with the exact JSONPath that solves it. Every example below uses a realistic payload — orders, webhooks, user accounts, validation errors — not the abstract bookstore. Paste any of them into the JSONPath Tester to see the result live.
A quick reminder before the recipes: JSONPath always returns an array of matches, even when a single value matches. So $.page returns [1], not 1. The tester here uses JSONPath‑Plus, which supports the RFC 9535 standard plus handy extensions like negative indices.
Follow along live: paste each payload and query into the tester.
Open JSONPath Tester →1. Pull one field from every item in a list
The most common API task: you have a paginated list and you want just the IDs (or names, or emails) from every object. Use a wildcard [*] to fan out across the array, then name the field.
{
"page": 1,
"orders": [
{ "id": 1001, "amount": 49.99, "status": "paid" },
{ "id": 1002, "amount": 129, "status": "paid" },
{ "id": 1003, "amount": 19.95, "status": "refunded" }
]
}
Query: $.orders[*].id
Result: [1001, 1002, 1003]
2. Filter an array by a numeric condition
Filter expressions use [?(…)], and @ refers to the current element. This is how you select "orders over $100" without looping in code.
Query: $.orders[?(@.amount > 100)]
Result: [{ "id": 1002, "amount": 129, "status": "paid" }]
Want just the matching IDs rather than the whole object? Append the field after the filter:
Query: $.orders[?(@.amount > 100)].id → Result: [1002]
3. Combine multiple conditions with && and ||
Filters support boolean logic. To find paid orders worth more than $50 (against the same payload above):
Query: $.orders[?(@.status == "paid" && @.amount > 50)].id
Result: [1002]
Order 1001 is paid but only $49.99, and 1003 is a refund — so only 1002 satisfies both conditions. Swap && for || when either condition should match.
4. Grab a value at unknown depth (recursive descent)
When you do not know — or do not care — how deeply a field is nested, the recursive descent operator .. searches the entire document.
{
"account": {
"owner": { "name": "Ana", "email": "ana@acme.io" },
"members": [
{ "name": "Ben", "email": "ben@acme.io" },
{ "name": "Cara", "email": "cara@acme.io" }
]
}
}
Query: $..email
Result: ["ana@acme.io", "ben@acme.io", "cara@acme.io"]
One expression collects the owner's email and both members' emails, despite living at different depths. Handy for pulling every email, id, or url out of a messy response. Need to explore the shape first? The JSON Viewer renders any payload as a collapsible tree so you can spot the path you need.
5. Extract a nested field from a webhook payload
Webhooks bury the useful data several levels down. Here is a trimmed Stripe‑style payment_intent.succeeded event:
{
"id": "evt_1",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_123",
"amount": 2000,
"currency": "usd",
"customer": "cus_42"
}
}
}
Query: $.data.object.amount → Result: [2000]
Query: $.data.object.customer → Result: ["cus_42"]
6. Get every value of a repeated key across mixed nesting
Recursive descent shines when the same key appears at multiple levels. Given a category → product tree:
{
"categories": [
{ "id": "c1", "products": [ { "id": "p1" }, { "id": "p2" } ] },
{ "id": "c2", "products": [ { "id": "p3" } ] }
]
}
Query: $..id → Result: ["c1", "p1", "p2", "c2", "p3"]
That grabs both category and product IDs. To scope it to product IDs only, anchor the descent at the products arrays:
Query: $..products[*].id → Result: ["p1", "p2", "p3"]
7. Take the first N, last, or every other element (slices)
JSONPath supports Python‑style slices [start:end:step] and negative indices. Given a list of recent events:
{ "events": ["login", "view", "click", "purchase", "logout"] }
| Query | Result |
|---|---|
$.events[0:3] | ["login", "view", "click"] (first three) |
$.events[-1:] | ["logout"] (last item) |
$.events[-2:] | ["purchase", "logout"] (last two) |
$.events[::2] | ["login", "click", "logout"] (every second) |
8. Select specific indices (union)
To pick non‑contiguous positions, list them in the brackets. Against the same events array:
Query: $.events[0,2] → Result: ["login", "click"]
9. Filter on whether a field exists (soft deletes / null)
A bare [?(@.field)] filter is a truthiness test, so it doubles as an "exists and is not null" check — perfect for soft‑delete flags.
{
"tasks": [
{ "id": 1, "title": "A", "deletedAt": null },
{ "id": 2, "title": "B", "deletedAt": "2026-01-05" },
{ "id": 3, "title": "C" }
]
}
Deleted tasks (has a non‑null deletedAt): $.tasks[?(@.deletedAt)].id → [2]
Active tasks (negate with !): $.tasks[?(!@.deletedAt)].id → [1, 3]
Task 1's deletedAt is null (falsy) and task 3 omits it entirely, so both count as active.
10. DevOps: the kubectl JSONPath dialect
You will meet JSONPath in kubectl output formatting — but it is a different dialect. It drops the leading $, wraps the path in { }, and does not support filter expressions.
kubectl get pods -o jsonpath='{.items[*].status.podIP}'
That lists every pod IP, space‑separated. The equivalent standard JSONPath you would test here is $.items[*].status.podIP. Knowing both saves confusion when a kubectl path "looks like" JSONPath but rejects a filter you copied from elsewhere.
11. API test assertions (Postman, REST Assured, Karate)
Most API testing frameworks let you assert on a JSONPath. Reusing the webhook payload from example 5, you assert that the charged amount is correct:
// REST Assured (Java)
given().when().get("/events/evt_1")
.then().body("data.object.amount", equalTo(2000));
// Karate
* match response.data.object.amount == 2000
// Postman test script (uses JS access to the same path)
pm.test("amount is 2000", function () {
pm.expect(pm.response.json().data.object.amount).to.eql(2000);
});
Prototype the path in the tester first, then drop it into your assertion — far faster than re‑running the whole suite to find a typo.
12. Pull error messages out of a 4xx response
Validation endpoints usually return an array of field errors. Extract just the human‑readable messages to surface in your UI or logs:
{
"error": "Validation failed",
"details": [
{ "field": "email", "message": "must be a valid email" },
{ "field": "age", "message": "must be >= 18" }
]
}
Query: $.details[*].message
Result: ["must be a valid email", "must be >= 18"]
Once you know the shape of an error body, generating a TypeScript interface for it makes handling these responses type‑safe in your client.
Common pitfalls
- Results are always arrays. Even
$.data.object.amountreturns[2000]. Read the first element when you need the scalar. - Recursive descent (
..) can be slow on very large documents because it visits every node — scope your path when you can. - Quote keys with special characters. A key with a space, hyphen, or leading digit needs bracket notation:
$['order-id'], not$.order-id. - Dialects differ. kubectl, JMESPath (AWS CLI), and standard JSONPath are not interchangeable — filters and the leading
$are the usual sticking points.
Frequently Asked Questions
How do I filter a JSON array with JSONPath?
Use a filter expression with the [?()] syntax, where @ refers to the current element. For example, $.orders[?(@.amount > 100)] returns every order whose amount is greater than 100. Combine conditions with && and ||, e.g. $.orders[?(@.status == "paid" && @.amount > 50)].
How do I get a value from a nested JSON object with JSONPath?
Chain keys with dot notation from the root $. For a Stripe‑style webhook, $.data.object.amount drills into data, then object, then amount and returns [2000]. If you do not know the full path, use recursive descent: $..amount finds every amount field at any depth.
How do I select the last item in an array with JSONPath?
Use a negative slice: $.events[-1:] returns the last element as a single‑item array. To take the last N items, widen the slice, e.g. $.events[-3:]; the first three are $.events[0:3]. (A bare $.events[-1] is not reliable across implementations — prefer the slice form.)
How do I extract all values of a field with JSONPath?
Use the recursive descent operator .. followed by the key name. $..email collects every email field anywhere in the document. To restrict the search to a known level, scope it first, e.g. $..products[*].id returns only product IDs, not category IDs.
Can JSONPath query an array of API objects?
Yes. To pull one field from every object in an array, use a wildcard: $.orders[*].id returns the ID of every order. Add a filter to narrow it, e.g. $.orders[?(@.status == "paid")].id returns only the IDs of paid orders. JSONPath always returns the matches as an array.
Does kubectl use the same JSONPath syntax?
Almost, but not exactly. kubectl uses a JSONPath dialect that omits the leading $, wraps the expression in curly braces, and does not support filter expressions. For example kubectl get pods -o jsonpath='{.items[*].status.podIP}' lists every pod IP. Standard JSONPath (and this tester) writes the same path as $.items[*].status.podIP.
Ready to test these against your own data?
Open JSONPath Tester →