JSONPath Explained: A Beginner's Guide with 15 Examples
JSON is everywhere — API responses, configuration files, event streams, database outputs. The harder part isn't getting JSON; it's extracting the specific piece of data you need from deeply nested structures without writing a chain of loops and conditionals.
JSONPath solves this the same way XPath solved it for XML decades ago. You write a compact expression that describes the path to the data you want, and the engine returns every match. No manual traversal, no temporary variables, no null checks on every intermediate node.
Here's every JSONPath expression you need to know, with real examples you can test immediately.
The Sample JSON
All examples use this data structure:
{
"store": {
"name": "TechBooks",
"location": {
"city": "San Francisco",
"state": "CA"
},
"books": [
{
"title": "JavaScript: The Good Parts",
"author": "Douglas Crockford",
"price": 29.99,
"tags": ["javascript", "programming"]
},
{
"title": "Clean Code",
"author": "Robert C. Martin",
"price": 35.00,
"tags": ["clean-code", "best-practices"]
},
{
"title": "Design Patterns",
"author": "Gang of Four",
"price": 45.00,
"tags": ["design-patterns", "architecture"]
}
],
"bestseller": {
"title": "You Don't Know JS",
"author": "Kyle Simpson",
"price": 39.99
}
},
"total_revenue": 1000000
}
JSONPath Syntax Overview
JSONPath expressions use a compact syntax similar to JavaScript property access, with extensions for wildcards, filters, and recursive matching.
| Symbol | Meaning | Example |
|---|---|---|
$ | Root object | $.store |
. | Child operator | $.store.name |
[] | Array index or subscript operator | $.store.books[0] |
.. | Recursive descent | $..price |
* | Wildcard (all children) | $.store.* |
@ | Current node (in filters) | ?(@.price > 30) |
?() | Filter expression | $..books[?(@.price < 40)] |
15 JSONPath Examples
Let's work through each example, from simple to complex.
Example 1: Root Object
$
Returns the entire JSON document. Useful as a sanity check — if this breaks, your JSON parser or JSONPath engine has a configuration problem.
Example 2: Dot Notation Child Access
$.store.name
Returns "TechBooks". The simplest query: navigate through the object tree one key at a time.
Example 3: Bracket Notation
$['store']['name']
Identical to Example 2. Bracket notation is required when the key contains spaces, special characters, or starts with a digit:
$["store"]["name"]
Both single and double quotes are generally accepted.
Example 4: Array Index
$.store.books[0]
Returns the first book object:
{
"title": "JavaScript: The Good Parts",
"author": "Douglas Crockford",
"price": 29.99,
"tags": ["javascript", "programming"]
}
Example 5: Multiple Array Indices
$.store.books[0,2]
Returns the first and third books as an array:
[
{ "title": "JavaScript: The Good Parts", ... },
{ "title": "Design Patterns", ... }
]
Example 6: Array Slice
$.store.books[1:3]
Returns books at index 1 (inclusive) through 3 (exclusive) — the second and third books:
[
{ "title": "Clean Code", ... },
{ "title": "Design Patterns", ... }
]
Slicing follows the same semantics as Python: [start:end:step]. Negative indices count from the end.
Example 7: Wildcard — All Array Elements
$.store.books[*]
Returns all three books as an array. Equivalent to $.store.books for arrays, but also works for objects where it returns all property values.
Example 8: Wildcard — All Object Children
$.store.*
Returns an array containing the store's property values: name, location, books, and bestseller.
Example 9: Recursive Descent — Find All Prices
$..price
Returns every price field at any nesting level:
[
29.99,
35.00,
45.00,
39.99
]
Recursive descent is JSONPath's most powerful feature. It searches the entire tree for any key matching the name, regardless of depth.
Example 10: Recursive Descent — Find All Titles
$..title
Returns all four titles (three from books array, one from bestseller):
[
"JavaScript: The Good Parts",
"Clean Code",
"Design Patterns",
"You Don't Know JS"
]
Example 11: Filter by Value
$.store.books[?(@.price < 35)]
Returns books with price less than 35:
[
{
"title": "JavaScript: The Good Parts",
"author": "Douglas Crockford",
"price": 29.99,
"tags": ["javascript", "programming"]
}
]
Example 12: Filter by String Comparison
$.store.books[?(@.author == "Douglas Crockford")]
Returns the matching book. The == operator handles string comparison. Some JSONPath implementations use === for strict comparison.
Example 13: Filter with Logical AND
$.store.books[?(@.price > 30 && @.price < 40)]
Returns books between $30 and $40:
[
{
"title": "Clean Code",
"author": "Robert C. Martin",
"price": 35.00
}
]
Example 14: Nested Array Access with Filter
$.store.books[?(@.tags[*] == "javascript")]
Returns books that have "javascript" in their tags array. The exact syntax varies by JSONPath implementation — some require @.tags and check membership differently. This is one area where JSONPath implementations diverge.
Example 15: Combine Recursive Descent with Filter
$..[?(@.price > 40)]
Finds any object at any level with a price greater than 40:
[
{
"title": "Design Patterns",
"author": "Gang of Four",
"price": 45.00
}
]
Dot Notation vs Bracket Notation
Both are equivalent for simple cases, but bracket notation handles edge cases that dot notation can't:
// Dot notation — fails if key has special characters
$.some-key // error: '-' interpreted as subtraction
// Bracket notation — works
$["some-key"]
Rules of thumb:
- Use dot notation for simple, alphanumeric keys
- Use bracket notation for keys with hyphens, dots, spaces, or numbers
- Use bracket notation when the key name is dynamic (stored in a variable)
Filter Expressions: The Full Operator Set
| Operator | Meaning | Example |
|---|---|---|
== | Equal (string or number) | ?(@.price == 35) |
!= | Not equal | ?(@.author != "Unknown") |
< | Less than | ?(@.price < 30) |
<= | Less than or equal | ?(@.price <= 35) |
> | Greater than | ?(@.price > 40) |
>= | Greater than or equal | ?(@.price >= 30) |
&& | Logical AND | ?(@.price > 10 && @.price < 50) |
|| | Logical OR | ?(@.price < 20 || @.price > 40) |
! | Logical NOT | ?(!@.isOutOfPrint) |
=~ | Regex match | ?(@.author =~ /Martin/i) |
Using JSONPath in Different Languages
JavaScript
const jsonpath = require('jsonpath');
const result = jsonpath.query(data, '$.store.books[?(@.price > 30)].title');
console.log(result);
// ["Clean Code", "Design Patterns"]
Python
from jsonpath_ng import parse
expr = parse('$.store.books[?(@.price > 30)].title')
result = [match.value for match in expr.find(data)]
print(result)
# ['Clean Code', 'Design Patterns']
Java
import com.jayway.jsonpath.JsonPath;
List<String> titles = JsonPath.read(data, "$.store.books[?(@.price > 30)].title");
System.out.println(titles);
// ["Clean Code", "Design Patterns"]
Command Line (jq-style, but JSONPath)
Some tools bridge JSONPath with command-line usage:
cat books.json | jsonpath "$.store.books[*].title"
JSONPath vs JMESPath vs XPath
If you're coming from a different query language background, here's how JSONPath maps to familiar concepts:
| Feature | JSONPath | JMESPath | XPath |
|---|---|---|---|
| Root | $ | @ | / |
| Child | .child | .child | /child |
| Recursive | .. | * | // |
| Wildcard | * | * | * |
| Filter | ?(@.x == y) | [?x == y] | [x = y] |
| Array index | [0] | [0] | [1] |
| Union | [0,1] | [0,1] | [1,2] |
The main differences:
- JMESPath has stronger typing and more sophisticated projection behavior
- XPath indexes from 1; JSONPath and JMESPath index from 0
- JSONPath's
..(recursive descent) is simpler than XPath's//axis but less precise - JMESPath supports function calls (
length(@),sort_by(@, &price)) - JSONPath filters use
@for the current node; JMESPath uses@for the root (confusing, I know)
Common JSONPath Mistakes
Mistake 1: Using Wrong Slice Syntax
// Wrong
$..[0:1]
// This returns the first child of every object/array in the tree!
Mistake 2: Filtering Without @
// Wrong
$.store.books[?(price > 30)]
// Right
$.store.books[?(@.price > 30)]
The @ references the current array element being evaluated. Without it, the filter doesn't know what object to check.
Mistake 3: Assuming Case Sensitivity
JSONPath key matching is case-sensitive by default. $..Title finds different results than $..title.
Mistake 4: Forgetting That Some Values Are Arrays
// Returns a single value
$.store.bestseller.price
// Returns an array of values
$.store.books[*].price
When your expression crosses an array boundary, the result is always an array. When it accesses a scalar value directly, the result is a single value.
Practice Exercise
Test your understanding. Given the sample JSON at the top of this article, write JSONPath expressions for each of these queries:
- Find the bestseller's author
- Find all book prices (including the bestseller)
- Find all books with "Design" in the title
- Find the city of the store
- Find all objects that have a tag
Answers (no peeking):
$.store.bestseller.author$..price$.store.books[?(@.title =~ /Design/i)]$.store.location.city$..[?(@.tags)]
Once you're comfortable with these expressions, you can test them against any JSON payload using the JSONPath tester. Paste your JSON, write your expression, and see the matched results instantly — faster than debugging a Python script or a JavaScript console.log chain.
FAQ
Is JSONPath part of the JSON specification?
No. JSONPath is a community standard with multiple implementations. The closest thing to an official specification is Stefan Gössner's original article and the ongoing IETF draft (draft-ietf-jsonpath-base).
What's the difference between $ and @?
$ always refers to the root of the JSON document. @ refers to the current node within a filter expression — the specific array element or object property being evaluated.
Does JSONPath support regex filters?
Most implementations do, using the =~ operator. Syntax varies: /pattern/flags in JavaScript implementations, different patterns in Python.
Can JSONPath modify JSON?
Most JSONPath implementations are read-only. They query but don't modify the data. Use JSON Patch (RFC 6902) or JSON Transform for mutations.
Why does my JSONPath expression return an array instead of a single value?
Because your path crosses an array boundary. Even if the array has one element, the result is an array. Use [0] to get the first element if you're sure it exists.
If you work with JSON APIs regularly, a good JSONPath tester is faster than manually inspecting responses in a browser's network tab. Paste the payload, write your expression, and confirm the result in seconds.