When you need a few values out of a JSON document, there are three common tools: JSONPath, a query language now standardised as RFC 9535; jq, a command-line processor with its own small programming language; and ordinary code, such as a JavaScript filter and map. They overlap but are not interchangeable. This article answers the same questions in all three on the bookstore document from RFC 9535, shows where each one fits, and lists the mistakes each one makes easy. The JSONPath answers can be run in the JSONPath tester, which loads that document as its sample.
The document
A store with four books (each with category, author, title and price, two also with an isbn) and a red bicycle priced 399. The book prices are 8.95, 12.99, 8.99 and 22.99.
Question 1: all authors
- JSONPath:
$.store.book[*].author - jq:
.store.book[].author - JavaScript:
data.store.book.map(b => b.author)
All three return Nigel Rees, Evelyn Waugh, Herman Melville and J. R. R. Tolkien. JSONPath returns a list of nodes, each with a location; jq emits four separate values (wrap the expression in [...] to get an array); JavaScript gives you an array directly.
Question 2: titles of books under 10
- JSONPath:
$..book[[email protected] < 10].title - jq:
.store.book[] | select(.price < 10) | .title - JavaScript:
data.store.book.filter(b => b.price < 10).map(b => b.title)
Result: "Sayings of the Century" and "Moby Dick". JSONPath also tells you where they are: $['store']['book'][0]['title'] and $['store']['book'][2]['title'], the "normalized paths" defined in RFC 9535. That is useful when the next step is to change the value in place or to report which record failed a check.
Question 3: every price, at any depth
- JSONPath:
$..price - jq:
[.. | objects | select(has("price")) | .price] - JavaScript: a recursive function that walks objects and arrays.
JSONPath's descendant operator .. is its strongest feature: 8.95, 12.99, 8.99, 22.99 and 399, in document order. In JavaScript you write and test the recursion yourself, and it is easy to forget arrays or to loop forever on shared references.
Question 4: books that have an ISBN
- JSONPath:
$..book[[email protected]] - jq:
.store.book[] | select(has("isbn")) - JavaScript:
data.store.book.filter(b => "isbn" in b)
A subtle difference: JSONPath's [email protected] tests that the member exists, so an isbn of null or "" still counts. In jq, select(.isbn) would drop null and false values, which is why has is the faithful translation. In JavaScript, b.isbn in a condition also drops empty strings and zero.
Question 5: something JSONPath cannot do
"The total price of all fiction books" needs arithmetic:
- jq:
[.store.book[] | select(.category == "fiction") | .price] | addgives 44.97. - JavaScript:
.filter(...).reduce((s, b) => s + b.price, 0)gives 44.97 as well (12.99 + 8.99 + 22.99). - JSONPath: no. It only selects; it never computes new values, builds objects or sums.
That is the dividing line. JSONPath is a selection language, deliberately small, so it can be embedded safely in configuration (AWS Step Functions use JSONPath paths such as $.detail to pick input; Kubernetes' kubectl -o jsonpath uses a related dialect). jq is a programming language for transforming data on the command line. JavaScript, or any general language, is for when the logic grows beyond one expression.
Choosing
| You need to | Use |
|---|---|
| Point at values in a config file or an API test | JSONPath |
| Know where each match is | JSONPath (normalized paths) |
| Transform, reshape or sum JSON in a shell script | jq |
| Logic with several steps, error handling, tests | Your programming language |
| Query JSON inside PostgreSQL | SQL/JSON path (jsonb_path_query), a different but similar language |
Common mistakes
- Mixing dialects. Many tools implemented JSONPath before the 2024 standard, each slightly differently. Script expressions such as
[(@.length-1)]and filters written[?(@.price < 10)]with extra rules come from older implementations. RFC 9535 uses[-1]and[[email protected] < 10](parentheses are still allowed around the condition). - Comparing a multi-valued query.
[[email protected][*] == 'sale']is an error in RFC 9535, because@.tags[*]can produce many values. Filter one level down, or usecount(). - Expecting type conversion.
[[email protected] == '42']does not match the number 42. - Forgetting that jq streams.
.store.book[].titleprints several JSON values, not an array; tools that expect one JSON document need[...]. - Using eval to run "JSONPath". Some old libraries evaluated filter expressions as JavaScript, which runs any code in the document or query. Standard implementations, including the tester, parse expressions instead.
Try it
Load the sample in the JSONPath tester, run the queries above and compare the paths with the results here. To pretty-print or validate the JSON first, use the JSON formatter.