RelicRELIC
Developer cheat sheets

JSONPath examples

JSONPath is XPath for JSON: a compact way to point at values deep inside a document. These examples run against one small store document from the root down through wildcards, slices, filters and recursive descent.

Jordan Gibbs July 10, 2026 8 min read

JSONPath is a query language for reaching into a JSON document and pulling out the parts you want. If you have used XPath for XML, the idea is the same, and the syntax borrows a lot of its shape. You write an expression that describes a path from the top of the document to the values you are after, and the engine returns whatever matches.

Rather than list operators in the abstract, this page threads one document through every example. Here it is, a tiny store with a few books and a bicycle. Every query below runs against exactly this.

the document (referred to as $ throughout)
{
  "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", "isbn": "0-553-21311-3", "price": 8.99 },
      { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 }
    ],
    "bicycle": { "color": "red", "price": 19.95 }
  }
}

The root and how to descend

Every JSONPath expression starts at $, the root of the document. From there you step into keys and arrays. There are two ways to name a key, and they are interchangeable except at the edges.

dot vs bracket notation
$.store.bicycle.color      dot notation
$['store']['bicycle']['color']   bracket notation
                            both return: "red"

Bracket notation earns its keep when a key is not a tidy identifier: keys with spaces, dots or hyphens have to go in brackets and quotes, as in $['first name']. Dot notation is shorter for everything else. Array elements use a numeric index in brackets, zero-based, so the first book’s title is a straight path down.

an array index
$.store.book[0].title
                            returns: "Sayings of the Century"

Wildcards

A wildcard *matches every element of an array or every value of an object. It is how you say “all of these” without naming each one. Both forms below fan out across the four books.

wildcard over an array
$.store.book[*].author
                            returns:
["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"]

Point a wildcard at an object instead and it walks that object’s values. Against the store, this returns the book array and the bicycle object side by side.

wildcard over an object
$.store.*
                            returns the book array and the bicycle object

Array slices

Slices borrow Python’s syntax: [start:end], where start is inclusive and end is exclusive. Leave a bound out to run to the edge, and use a negative index to count from the back.

slices
$.store.book[0:2]     the first two books (indexes 0 and 1)
$.store.book[:2]      same thing, start defaults to 0
$.store.book[-1:]     the last book only
$.store.book[1:]      every book except the first

Selecting several named keys at once

Bracket notation does one more thing dot notation cannot: it takes a comma-separated union of keys or indexes, pulling several specific elements in one expression. This is handy when you want two named fields, or a scattered set of array positions, without a wildcard sweeping up everything.

a union of indexes
$.store.book[0,2].title
                            returns:
["Sayings of the Century", "Moby Dick"]

The same union works on object keys, so $.store['bicycle','book'] returns both branches of the store. Support for unions is one of the more consistent features across engines, though the exact output ordering can differ, which matters if you depend on the order.

Filters

Filters are where JSONPath gets genuinely useful. A filter expression ?(...) keeps only the elements for which the condition is true. Inside the filter, @ refers to the element currently being tested, so you compare its fields.

filter by price
$.store.book[?(@.price < 10)]
                            returns the two books under $10:
                            "Sayings of the Century" and "Moby Dick"

A filter can also test for the presence of a field. This next one keeps only books that carry an ISBN, which drops the first two.

filter by field existence
$.store.book[?(@.isbn)]
                            returns "Moby Dick" and "The Lord of the Rings"

Filters combine with the other operators. Add a trailing key to pull one field off each surviving element, so you get a clean list of titles rather than whole objects.

filter, then project a field
$.store.book[?(@.category == 'fiction')].title
                            returns:
["Sword of Honour", "Moby Dick", "The Lord of the Rings"]

Recursive descent

The double dot ..searches at any depth. It is the tool for “find every X, wherever it lives,” without spelling out the path. Against the store, this collects every price in the document, from both the books and the bicycle.

recursive descent
$..price
                            returns: [8.95, 12.99, 8.99, 22.99, 19.95]

Recursive descent pairs naturally with a wildcard or a filter. $..author gathers every author anywhere in the tree, and $..book[?(@.price > 20)] finds pricey books no matter how deeply the book array is nested. It is powerful, and on large documents it is also the slowest operator, since it visits every node, so prefer an explicit path when you know one.

A precision note: .. can surprise you by matching more than you expect, because it descends into every object and array. If $..price returns extra values, something else in the document has a price key too. Narrow it with an explicit parent, like $.store.book[*].price, when you want only one kind.

Implementations vary

JSONPath started as a 2007 blog post, not a formal spec, so for years every library implemented it a little differently. That changed in 2024, when the IETF published RFC 9535, a real standard for JSONPath. Newer libraries are converging on it, but plenty of code in the wild still predates it.

The core, everything above through recursive descent, is portable. Divergence concentrates in the filter syntax and in a few edge behaviors:

  • Filter comparison operators: some engines want ==, some accept a single =, and support for regex matching or string functions inside a filter is very uneven.
  • Quoting inside filters: RFC 9535 uses single quotes for string literals; older engines mixed single and double, so a filter that works in one tool can fail to parse in another.
  • Result shape: whether a single-match query returns a scalar or a one-element list differs, which trips up downstream code that assumed one or the other.

The practical rule: keep expressions simple, prefer == and single quotes in filters, and test against the exact library you will run in production. When you are just eyeballing structure to find the right path, a formatter helps first; our JSON formatter and JSON validator both run locally in the browser, and the JSON diff is handy when two responses should match but do not.

How to read a query, and how to build one

Reading a JSONPath expression is easiest left to right, as a series of steps down the tree. Take $.store.book[?(@.price < 10)].title: start at the root, step into store, then book, keep only the elements where the price is under ten, and from each survivor pull the title. Every operator narrows or fans out the set of nodes you are holding, and the final one decides what comes back.

Building one runs the same way in reverse. Decide what you want out, then walk backward to the root, asking at each level whether you know the exact key (a plain path), want all children (a wildcard), or want a subset (a filter or a slice). Writing the path in that order tends to produce something simpler than reaching for recursive descent by reflex, and simpler expressions are the ones that behave the same across libraries.

One behavior shapes a lot of downstream code: most engines return a listof matches, sometimes called a nodelist, even when only one node matches. Code that reads a single value out of a JSONPath result usually has to index into that list, and forgetting to is a frequent source of “why is my value wrapped in an array” confusion.

JSONPath versus jq

JSONPath and jq overlap but aim at different jobs. JSONPath is a selection language, best embedded in another tool or config to point at values. jq is a full transformation language with pipes, functions and its own logic, better on the command line when you need to reshape data as well as extract it. If you are querying a config file or a policy engine, you probably want JSONPath; if you are munging an API response in a terminal, jq usually wins.

For the regex-shaped parts of both, and the flavor gotchas that come with them, the cluster pillar, the regex cheat sheet, is the companion piece.

Written by
Jordan GibbsFounder, Relic

Jordan Gibbs is the founder of Relic, an end-to-end encrypted, permanent, searchable memory for everything you copy. He writes widely about AI, agents, and practical tooling on Medium, where he is read by tens of thousands, and builds privacy-first software. Here he covers how everyday tools like the clipboard actually work, and how to use them without handing your data to someone else.

MediumGitHubLinkedIn
Part of
Developer cheat sheets
Keep reading
Pillar·9 min

Regex cheat sheet

The regex pieces you actually reach for, character classes through lookarounds, with the flavor differences that bite you and a handful of patterns worth saving.

Read
8 min

jq cheat sheet

jq is sed for JSON. The filters you use most, built up against one API response so every command has a visible result, from map and select to group_by and raw output.

Read
8 min

wget vs curl: what is the difference?

wget and curl both fetch over HTTP, and there the resemblance ends. One is a recursive downloader, the other a protocol swiss army knife. What each is genuinely better at.

Read
developerjsonreference