Mastering JSONPath: Query JSON Like a Pro
Learn JSONPath from the ground up — root, child, recursive descent, wildcards, array slices, and filter expressions — with real nested JSON and dozens of examples.
JSONPath is a compact query language for JSON, much like XPath is for XML. Instead of writing loops to dig through nested objects and arrays, you describe the path to the data you want and let the engine fetch it. This guide walks through every core piece of the syntax with a single realistic dataset so you can see exactly what each expression returns.
The sample data
Every example below runs against this store document. Paste it into the JSONPath tester so you can follow along and experiment as you read.
{
"store": {
"name": "Riverside Books",
"books": [
{ "title": "The Pragmatic Programmer", "author": "Hunt", "price": 39.99, "tags": ["dev", "classic"] },
{ "title": "Clean Code", "author": "Martin", "price": 32.50, "tags": ["dev"] },
{ "title": "Deep Work", "author": "Newport", "price": 18.00, "tags": ["focus", "career"] },
{ "title": "Sapiens", "author": "Harari", "price": 24.99, "tags": ["history"] }
],
"bicycle": { "color": "red", "price": 199.95 }
},
"inStock": true
}
The root: $
Every JSONPath expression starts at the root of the document, represented by the dollar sign $. On its own, $ simply selects the entire document.
$ -> the whole JSON document
From the root you walk down into the structure using the child operator.
Child access with . and [ ]
The dot operator selects a named child. You can chain dots to descend several levels at once.
$.store.name -> "Riverside Books"
$.store.bicycle.color -> "red"
$.inStock -> true
Bracket notation is equivalent and is required when a key contains spaces, dots, or other awkward characters. These two expressions are identical:
$.store.name
$['store']['name']
When you select an array, you can index into it with brackets. Indexes are zero-based, and negative indexes count from the end.
$.store.books[0].title -> "The Pragmatic Programmer"
$.store.books[2].author -> "Newport"
$.store.books[-1].title -> "Sapiens"
Recursive descent with ..
The double-dot operator is where JSONPath starts to feel powerful. It searches every level of the document for a matching key, no matter how deeply nested. You do not have to know the exact path.
$..price
Result — every price field anywhere in the tree:
[39.99, 32.5, 18.0, 24.99, 199.95]
Notice it pulled prices from both the books array and the bicycle object. Recursive descent is ideal when a key appears in several places or when the structure varies. You can also combine it with a child step:
$..books[0].title -> "The Pragmatic Programmer"
$..author -> ["Hunt", "Martin", "Newport", "Harari"]
Wildcards with *
The wildcard * matches every element of an array or every value of an object. Use it when you want all children at a given level.
$.store.books[*].title
Result:
["The Pragmatic Programmer", "Clean Code", "Deep Work", "Sapiens"]
Applied to an object, the wildcard returns the values rather than the keys:
$.store.bicycle.*
["red", 199.95]
You can mix the wildcard with recursive descent to grab everything of a kind across the whole tree:
$..* -> every value and sub-structure in the document
Array slices
JSONPath borrows Python-style slicing with the start:end:step form. The start index is inclusive and the end index is exclusive.
$.store.books[0:2].title
Result — the first two titles:
["The Pragmatic Programmer", "Clean Code"]
More slice variations against the same books array:
$.store.books[:2] -> first two books
$.store.books[2:] -> from index 2 to the end
$.store.books[-2:] -> last two books
$.store.books[::2] -> every other book (step of 2)
A step of 2, for instance, returns books at indexes 0 and 2 — "The Pragmatic Programmer" and "Deep Work".
Filter expressions with ?(...)
Filters are the most expressive part of JSONPath. The syntax ?(...) keeps only the elements for which the inner expression is true. Inside the filter, the @ symbol refers to the current element being tested.
Suppose we want every book that costs more than 30. The filter expression is ?(@.price > 30):
$.store.books[?(@.price > 30)].title
Result:
["The Pragmatic Programmer", "Clean Code"]
You can compare against strings, check fields for existence, and combine conditions. Here are several practical filters, each with the comparison described in words so it reads cleanly.
Books priced at less than 20 (the expression is @.price < 20):
$.store.books[?(@.price < 20)].title -> ["Deep Work"]
Books by a specific author:
$.store.books[?(@.author == 'Newport')].title -> ["Deep Work"]
Books that have a tags field at all:
$.store.books[?(@.tags)].title -> all four titles
Combining conditions with logical AND (&&) — books that cost more than 20 and are tagged dev. Many engines support the regex or contains style match shown here:
$.store.books[?(@.price > 20 && @.tags)].title
Logical OR uses ||. Filters can also reference the root with $, which is handy for comparing an element against a value elsewhere in the document.
Counting and existence patterns
Filters are commonly used just to test whether anything matches. If the result array is empty, nothing qualified. For example, to confirm whether any book costs more than 100:
$.store.books[?(@.price > 100)] -> []
An empty array tells you no book exceeded that price, while the bicycle would match the same filter applied at the store level.
Putting it together
Real queries chain these operators. To get the titles of every dev-tagged book under 35, you might write a recursive filter, and to pull just the authors of the two cheapest entries you would slice then project. Building these incrementally is far easier with instant feedback. Drop your data into the JSONPath tester, type an expression, and watch the matches highlight in real time. If the JSON itself is hard to read while you work, open it in the JSON viewer for a collapsible tree view first.
A quick mental checklist when an expression returns nothing:
- Did you start with
$? - Are array indexes zero-based and within range?
- Inside a filter, did you use
@for the current item rather than$? - Are string literals in single quotes inside the filter?
Conclusion
JSONPath turns tedious nested-object navigation into short, declarative expressions. Start with the root $, descend with . and [ ], reach anywhere with .., broaden with *, narrow with slices, and finally pin down exactly what you need with ?(...) filters. Once these pieces click, you can extract almost anything from a payload in a single line.
Ready to practice? Open the JSONPath tester, paste the store sample above, and try rewriting every example in this post yourself. It is the fastest way to build real fluency.
Keep reading
Validating JSON with JSON Schema
Learn how JSON Schema works, how to generate a schema from sample data, and how to validate documents with clear, path-based error messages.
JSON vs JSON5 vs JSONC — What's the Difference?
Comments, trailing commas, and unquoted keys — understand JSON5 and JSONC, where each is used, and how to convert them to strict JSON.
How to Format JSON (and Why It Matters)
A practical guide to beautifying, indenting, and minifying JSON — and when to use each, with tips for debugging messy API responses.