Skip to content
jsonforge.app
Back to blog
Guide8 min read

JSONPath and Querying JSON: Finding Data Deep in Large Documents

Most JSON you meet in production is deep, wide, and bigger than your screen. JSONPath lets you describe what you want ("every price in every book") as one path expression instead of a page of nested loops and null checks. This guide covers the syntax that matters, a realistic worked example, and the trade-offs against jq and application code.

Why a query language for JSON?

Retrieving data from nested JSON in application code means chained property access, null checks at every level, and loops as soon as an array appears. It works, but it spreads knowledge of the document's structure across your codebase. Move a field and you're hunting through five files.

A query language inverts that: you express the location or pattern of what you need, and the library does the traversal. JSONPath is the most widely deployed option, available in some form for JavaScript, Java, Python, Go, and C#, and supported natively inside databases like PostgreSQL and SQL Server.

Core syntax: $, ., .., and brackets

Every JSONPath expression starts from $, the root of the document. From there, .name selects a child key (bracket notation ['name'] means the same thing), [n] indexes into an array, and .. performs recursive descent, matching the key at any depth. The catalog below serves as the running example for this guide.

A store catalog: small, but deep enough to exercise every part of the syntax.
json
{
  "store": {
    "name": "JsonPath Books",
    "banners": ["summer-sale", "new-arrivals"],
    "book": [
      {
        "category": "reference",
        "title": "Sayings of the Century",
        "author": { "name": "Nigel Rees", "country": "UK" },
        "price": 8.95,
        "in_stock": true
      },
      {
        "category": "fiction",
        "title": "Sword of Honour",
        "author": { "name": "Evelyn Waugh", "country": "UK" },
        "price": 12.99,
        "in_stock": false
      },
      {
        "category": "fiction",
        "title": "Moby Dick",
        "author": { "name": "Herman Melville", "country": "US" },
        "isbn": "978-0-14-243724-7",
        "price": 8.99,
        "in_stock": true
      }
    ],
    "mug": { "color": "red", "price": 19.95 }
  }
}

Against this document, $.store.name returns "JsonPath Books" and $.store.book[0].title returns the first title. The expression $..title collects every title anywhere in the document, at any depth. Plain dot notation can't give you that recursive descent.

Wildcards, slices, and unions

The * wildcard selects all children of a node: $.store.book[*] is the whole book array, $.store.book[*].author.name is every author name, and $.store.* matches both book and mug. Negative indexes count from the end, so $.store.book[-1].title is "Moby Dick".

Slices borrow Python's convention: [start:end] selects a half-open range of array elements, with an optional third value for the step. The expression $.store.book[0:2] returns the first two books, $.store.book[1:] everything after the first, and $.store.book[-2:] the last two. Unions select several children at once, by index like $.store.book[0,2] or by key like $.store.book[0]['title','price'], though implementations still disagree on union support.

The expressions you'll use most, against the catalog above.
plaintext
$.store.name                          ->  "JsonPath Books"
$.store.book[0].title                 ->  "Sayings of the Century"
$.store.book[-1].title                ->  "Moby Dick"
$.store.book[*].price                 ->  [8.95, 12.99, 8.99]
$.store.book[0:2]                     ->  the first two book objects
$..author.name                        ->  all three author names, at any depth
$..[?(@.price)]                       ->  every object carrying a price

Filter expressions

Filters turn JSONPath from a locator into a query. A filter is a bracketed expression with a question mark, and @ refers to the object under test: $.store.book[?(@.price < 10)] returns the two books under 10, "Sayings of the Century" and "Moby Dick".

Existence checks select objects that carry a key: $..book[?(@.isbn)] finds "Moby Dick", the only book with an ISBN, and skips books without one instead of matching them as null. Comparisons support ==, !=, <, <=, >, and >=, and the left side can be a path as well as a key: $.store.book[?(@.author.country == 'UK')].title returns the two UK titles.

Filters compose with recursive descent for heterogeneous documents: $..[?(@.in_stock == true)] matches any in-stock object, anywhere. Hand-written traversal code struggles to express that without knowing the schema up front, and JSONPath was designed for that situation.

JSONPath vs jq vs application code

JSONPath is an expression; jq is a language. jq can filter, reshape, group, sort, and compute with piped stages like .store.book | map(select(.price < 10)) | map(.title), which makes it the stronger choice for ad-hoc terminal work and pipeline steps. But it runs as a separate process, and shell invocations embedded in production code become a maintenance trap.

JSONPath wins when the query lives inside a program or a configuration: feature-flag targeting rules, policy engines, test assertions, mock-server request matchers. You can store, validate, and evaluate it as a string instead of shelling out to a subprocess.

Plain code wins when the logic needs context the document can't provide: joins against other data, permission checks, or a transformation so stateful that a declarative path would obscure it. A rule of thumb: use JSONPath to locate, and code to decide.

Pitfalls: the spec situation and library drift

For two decades JSONPath had no normative specification beyond Stefan Goessner's original example page, so implementations diverged freely. RFC 9535, published in February 2024, standardizes the syntax, but many popular libraries predate it. Filter capabilities, union support, string quoting, and even key-name case sensitivity vary between them. Read your library's compatibility notes before shipping an expression.

Result shapes vary as much. Some libraries return null for a failed match, some return an empty array, and some throw. JavaScript libraries usually return an array of matches even when at most one result is possible, while PostgreSQL offers both jsonb_path_query and jsonb_path_query_first. Any code that branches on a match result needs to know which convention applies.

Performance is the final trap. Recursive descent and filters scan the whole document, so a $..[?(@.price)] over a multi-megabyte file traverses everything on each evaluation. For hot paths, extract the data once into an index, or push the query down into the database where the data already lives.

FAQ

Is JSONPath an official standard?
Partly. RFC 9535, published in February 2024, defines the query syntax normatively, but it arrived long after most libraries were written, so support is uneven. Many implementations still follow the original Goessner draft from the 2000s, which is looser about filters and unions. Check your library against the RFC before relying on the newer features.
What is the difference between . and .. in JSONPath?
A single dot is a direct child lookup: $.store.name requires name to sit one level under store. Two dots perform recursive descent: $..name matches name at any depth below the root, including inside array elements. Use .. when you don't know or care where the key lives, and . when the structure is a contract you want enforced.
Can JSONPath modify a document, or only read it?
Almost all implementations are read-only, and RFC 9535 covers queries only. A few libraries bolt on update or patch operations, but those are non-standard. To transform JSON instead of locating data inside it, use jq or ordinary code in your language.
Do any databases support JSONPath natively?
Yes. PostgreSQL exposes JSONPath through jsonb_path_query and jsonb_path_query_first, SQL Server through JSON_VALUE and JSON_QUERY with its own path dialect, and SQLite's json_extract covers the common subset. Pushing the query into the database usually beats shipping the document to your application, especially for large documents.

Try these tools

Related articles