Skip to main content
Reference · 8 min read

JSONPath Explained, With Runnable Examples

A complete JSONPath reference: root, children, wildcards, slices, recursive descent and filters — with a worked example for each.

JSONPath is a query language for JSON. Stefan Gössner proposed it in 2007 as an analogue of XPath, and it has since turned up nearly everywhere: kubectl -o jsonpath, Postman assertions, AWS CLI queries, Elasticsearch pipelines, Camel routes, countless API testing tools. RFC 9535 standardised it in 2024.

Every example below runs against this document. Paste it into the JSONPath evaluator and follow along.

{
  "store": {
    "book": [
      { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 },
      { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 },
      { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "price": 8.99, "isbn": "0-553-21311-3" }
    ],
    "bicycle": { "color": "red", "price": 19.95 }
  }
}

The building blocks

$ — the root

Every expression starts at $, the whole document. $ on its own returns the document itself.

. and [""] — child access

$.store.bicycle.color returns "red". Use bracket notation when a key is not a plain identifier: $["store"]["bicycle"]["color"], or $.data["user-id"].

[n] — array index

$.store.book[0].title returns "Sayings of the Century". Negative indices count from the end: $.store.book[-1] is the last book.

[*] — wildcard

$.store.book[*].author returns all three authors. Applied to an object, $.store.* returns every value of store.

[start:end:step] — slice

$.store.book[0:2] returns the first two books. $.store.book[-2:] returns the last two. $.store.book[::2] returns every other book.

.. — recursive descent

The most useful operator in the language. $..price returns every price anywhere in the document — the three book prices and the bicycle. $..author finds authors regardless of how deeply they are nested, which is exactly what you want when you do not know the shape of a response.

[?(...)] — filter

A filter tests each item, with @ bound to the item under test.

$.store.book[?(@.price < 10)]            two cheap books
$.store.book[?(@.category=="fiction")]   two fiction books
$.store.book[?(@.isbn)]                  only the book that has an isbn
$..[?(@.price > 10)]                     anything priced over 10

Recipes

  • Every email in a response$..email. Works no matter where they are nested.
  • One field from every record$.data[*].id.
  • Records matching a condition$.users[?(@.active==true)].
  • Every object that has a given field$..[?(@.deletedAt)].
  • The last item$.items[-1:].
  • Two named fields$.user["name","email"].

Building an expression without guessing

The reliable way to write a JSONPath query is to stop typing key names from memory. Open the document in the viewer, expand to the value you want, and copy its path — you get something like $.data[3].preferences.theme. Replace the concrete index with [*] and you have a query for every record.

Dialect differences

JSONPath was a blog post before it was a standard, so implementations disagree at the edges. Things to check before relying on a query in production:

  • Whether a single match returns a value or a one-element list.
  • Whether $.array.length works, or whether you need $.array[-1:] and a count.
  • Whether filter expressions support regular expressions — many do not.
  • Whether script expressions [(...)] are supported at all. They should not be: they evaluate arbitrary code.

JSONPath or jq?

JSONPath selects. jq selects *and* transforms, reshapes, aggregates and formats. If you only need to pull values out of a document, JSONPath is shorter and is what your API tooling already supports. If you need to build a new document from an old one, use jq.