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.
{
"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.
$.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 priceFilter 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
What is JSON? A Complete Beginner's Guide →
JSON (JavaScript Object Notation) is the lightweight data-interchange format behind nearly every API, config file, and NoSQL database. Learn the syntax, the six data types, how parsing behaves in code, and the mistakes everyone makes hand-writing it.
The History of JSON: From 2000 to Industry Standard →
Trace JSON from Douglas Crockford's idea in 2001, through Yahoo's adoption, to becoming the ECMA-404 standard that powers 90% of modern APIs.
The Complete JSON Schema Guide (Draft 7) →
JSON Schema is the standard for describing and validating JSON structure. Learn the core keywords, build a real API schema, compose and reuse schemas with $ref, and run validation in code with Ajv.