JSONPath Examples: Real-World Queries for API Responses

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"] }
QueryResult
$.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.amount returns [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 →
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds developer tools at JSON Dev Tools.