fileview.dev

JSONPath, with worked examples

10 min read

Written alongside a JSONPath implementation with a visual builder, so the divergences listed below are ones that had to be resolved rather than read about.

JSONPath is a query language for JSON, in the way XPath is one for XML. An expression starts at the root, walks down through keys and array indices, and returns every value that matches — which may be none, one, or many.

The reason to learn JSONPath is that pulling one field out of a deeply nested API response should not require writing a loop. The reason it frustrates people is that it looks like a settled standard and is not: it began as a blog post in 2007, and the formal specification arrived only in 2024. Everything below notes where that history still shows.

The document these examples run against

{
  "store": {
    "name": "Northgate",
    "orders": [
      { "ref": "A-1", "total": 42.5, "paid": true,
        "lines": [{ "sku": "PEN", "qty": 3 }, { "sku": "PAD", "qty": 1 }] },
      { "ref": "A-2", "total": 118.0, "paid": false,
        "lines": [{ "sku": "DESK", "qty": 1 }] },
      { "ref": "A-3", "total": 9.99, "paid": true,
        "lines": [{ "sku": "CLIP", "qty": 20 }] }
    ]
  }
}
A small order document

Selectors

Expressions and what they return from the document above
ExpressionReturns
$The whole document
$.store.name"Northgate"
$.store.orders[0].ref"A-1"
$.store.orders[-1].ref"A-3" — negative indices count from the end
$.store.orders[*].refAll three references
$..skuEvery sku anywhere in the document
$.store.orders[0,2].ref"A-1" and "A-3" — a union of indices
$.store.orders[:2].refThe first two references — a slice
$.store.orders[1:].totalTotals from the second order onward
$..lines[*].qtyEvery quantity, flattened across orders

Bracket notation is the same thing in a more verbose form, and it is the one to reach for when a key contains a space, a dot or a hyphen. Dot notation cannot express those, so $["order-total"] is not a stylistic choice but the only option.

Recursive descent

Two dots search every level below the current node rather than one. It is the most useful operator in the language and the easiest to over-use: on a large document it visits every node, and it will happily match a key of the same name in a part of the tree you did not have in mind.

Filters

A filter expression selects items matching a condition. Inside it, the current item is @, and comparisons work the way you would expect. This is where JSONPath stops being a path and starts being a query.

Filter expressions
ExpressionReturns
$.store.orders[?(@.paid == true)].ref"A-1" and "A-3"
$.store.orders[?(@.total > 100)].ref"A-2"
$.store.orders[?(@.total > 10 && @.paid)].ref"A-1"
$.store.orders[?(@.ref == "A-2")].total118.0
$..lines[?(@.qty >= 3)].sku"PEN" and "CLIP"
$.store.orders[?(@.discount)]Orders where the key exists at all

The last row is worth noting: a bare @.key inside a filter is an existence test, not a truth test. It matches whenever the key is present, including when its value is false or 0, which is a common source of results that look one too many.

Where implementations disagree

RFC 9535 finally standardised the language in 2024, but the libraries that predate it are still everywhere. If an expression works in one tool and not another, it is almost always one of these.

Known divergences between implementations
AreaThe disagreement
Root of a matchSome return the matched values, others return their paths
No matchSome return an empty list, others return null, others raise
Parent accessA parent operator exists in some libraries and is not in the RFC
Script expressionsOlder libraries evaluate JavaScript inside filters; the RFC does not
Regular expressionsA match operator is common but was spelled differently everywhere
Union orderingWhether results follow document order or the order written

Mistakes that account for most failures

Try an expression against your own fileWith a visual builder for the syntax nobody memorises.

Questions

What is JSONPath used for?

Extracting specific values from a JSON document without writing traversal code — pulling one field out of a large API response, filtering a list by a condition, or collecting every occurrence of a key across a nested structure.

What is the difference between one dot and two in JSONPath?

A single dot moves one level down to a named child. Two dots search every level below the current node, so it finds a key however deeply it is buried — at the cost of also finding it in places you did not intend.

How do I filter an array in JSONPath?

Use a filter expression, where @ refers to the item being tested — for example selecting orders whose total exceeds one hundred. Comparison and boolean operators work inside it, and the result is always a list even when one item matches.

Is JSONPath a standard?

It is now. RFC 9535 standardised it in 2024, seventeen years after the original proposal. Many widely used libraries predate it and differ on filters, error handling and whether a query returns values or paths.