← Back to Blog

JSONPath in Practice: Pinpointing Values in Nested JSON

A real extraction request

The backend returns a deeply nested order response, and you want to pull out, in one go, "all order IDs with status paid plus their totals":

{
  "orders": [
    { "id": "o1001", "status": "paid",     "total": 128.5 },
    { "id": "o1002", "status": "pending",  "total": 59.0  },
    { "id": "o1003", "status": "paid",     "items": [
        { "sku": "A-01", "price": 39.9 },
        { "sku": "A-02", "price": 20.1 }
      ], "total": 60.0 }
  ]
}

In code you would write a for loop plus a condition plus a collect list—several lines. With JSONPath, one line:

$.orders[?(@.status == "paid")]. {id, total}

No single plain path can express that—it needs traversal + filtering + projection. JSONPath is designed so you can do exactly this in one string, which is perfect for API debugging, log cleaning and config extraction.

Syntax quick reference

Expression Meaning Returns
$ root node the whole document
.key / ['key'] child property that property
* wildcard all children / elements
..key recursive descent matching keys at any depth
[0] array index that element
[0,2] multiple indexes elements 0 and 2
[start:end] slice the range
[?(@.k==v)] filter elements matching the condition

Walking through examples

$                      → the whole object
$.orders[1].id         → "o1002"
$.orders[*].id         → ["o1001","o1002","o1003"]
$.orders[?(@.total>60)].id
                       → ["o1001","o1003"]
$-orders..price        → [39.9,20.1]        (collect every price recursively)

Note how ..price also digs out the price nested inside items—that is exactly the value of recursive descent.

Key concepts: $ and @

  • $ always starts from the root; $.a.b.c is an explicit path;
  • @ means "the current element" only inside a filter;
  • Mixing them lets you "filter a subset by condition, then pick a field inside it".

Going further: expressions and logic

$.orders[?(@.status=="paid" && @.total>=60)].id
$..[?(@.status=="paid")]                 # filter paid at any depth

Combine conditions with boolean logic, and you can even filter the whole document recursively—that is how JSONPath finders in tool sites let you write a screen in one line.

What if the match is empty

The query syntax does not validate whether a key exists; two common situations:

  1. Returns []: the path is valid but nothing matched (e.g. no element passed the filter);
  2. Throws: the path itself is illegal (e.g. an index applied to something that is not an array).

To judge whether a JSONPath is correct, first format the JSON so the structure is readable, then break the path into segments and widen each level with wildcards to see where it breaks. That is exactly the "format first, then JSONPath search" combination.

A practical workflow

When debugging a third-party API, my extraction order is:

  1. Format the response first to see the overall levels (avoid counting braces in minified JSON);
  2. Use coarse wildcards like $[*]. to confirm field-name spelling;
  3. Then land on an exact path or a filter expression;
  4. Cross-check: run the same path against a JSON diff to confirm two responses are consistent.

Self-check

Throw the opening orders example into any JSONPath tool:

  • $.orders[*].id should return the 3 ids;
  • $.orders[?(@.status=="paid")].id should return 2 (o1001, o1003);
  • $.orders..price should also pull out the nested 39.9 and 20.1.

If all three are right, your intuition for wildcards, recursion and filters is solid.

Frequently Asked Questions

What is the difference between JSONPath and JSON Pointer?

JSONPath is a query language closer to SQL/regex: it supports wildcards (`*`), recursive descent (`..`), array indexes and filter expressions (`[?(@.age>18)]`), returning a set of matched nodes—great for mining nested JSON. JSON Pointer is a strict locator like `/a/b/0` that points to one exact predetermined node with no wildcards or filters; it is mainly used by JSON Patch and error reporting. In short: use JSONPath to query, JSON Pointer to locate.

What exactly does recursive descent `..` do?

`..name` means 'depth-first traverse the whole document and collect every key named name regardless of depth'. It is ideal when you don't care how deeply a field is nested and just want all prices or IDs. The trade-off: results come back in traversal order (possibly unordered across objects) and it is slower than a targeted path on large JSON. When you know the exact depth, prefer a full path (`$.a.b.c`); use recursive descent when nesting is unknown.

What does `@` mean inside a filter like `[?(@...)]`?

Inside a filter expression, `@` refers to the current array element being visited. `$.users[?(@.age > 30)].name` runs the filter over each element (where `@` is that element), keeps those with `@.age>30`, then projects their `name`. `?` marks a filter and `(...)` holds a boolean condition, supporting comparisons, logical `&&`/`||`, and field-existence checks like `@.field`. This screening power is where JSONPath separates from plain path extraction.

← Back to Blog