JSONPath Tutorial: Query JSON Like a Pro
Master JSONPath syntax with practical examples. Learn every operator, filter expression, and real-world query pattern to extract data from complex JSON structures.
What Is JSONPath?
JSONPath is a query language for JSON, similar to what XPath does for XML. It lets you reach into a complex JSON structure and pull out exactly the data you need using a concise expression syntax. Instead of writing loops and conditionals to dig through nested objects and arrays, you write a single path expression like $.store.book[?(@.price < 10)].title and get back every book title under ten dollars.
Stefan Goessner introduced JSONPath in 2007, and it has since become a de facto standard across languages and tools. If you work with REST APIs, configuration files, or any non-trivial JSON data, JSONPath will save you significant time.
JSONPath vs. XPath: A Quick Comparison
If you already know XPath, JSONPath will feel familiar. The key difference is that JSON has a simpler data model — objects and arrays rather than elements, attributes, and namespaces. Here is how the two compare:
| Concept | XPath | JSONPath |
|---|---|---|
| Root | / | $ |
| Child | /child | .child or ['child'] |
| Recursive descent | // | .. |
| Wildcard | * | * |
| Array index | [1] (1-based) | [0] (0-based) |
| Filter | [predicate] | [?(expression)] |
The Complete JSONPath Operator Reference
Every JSONPath expression starts from the root $ and chains operators to navigate deeper. Here is every operator you need to know:
| Operator | Description | Example |
|---|---|---|
$ | The root object or array | $ — the entire document |
.key | Child member by name (dot notation) | $.store.name |
['key'] | Child member by name (bracket notation) | $['store']['name'] |
.. | Recursive descent — searches all descendants | $..price — every "price" field |
* | Wildcard — all members of an object or array | $.store.* |
[n] | Array index (0-based) | $.items[0] |
[start:end] | Array slice (end is exclusive) | $.items[0:3] — first three items |
[start:end:step] | Array slice with step | $.items[::2] — every other item |
[n,m] | Multiple array indices | $.items[0,2,4] |
[?()] | Filter expression | $.items[?(@.price > 10)] |
@ | Current node (used inside filters) | @.name == 'Alice' |
Sample JSON for Our Examples
We will use this JSON document throughout the tutorial. It represents a small bookstore API response:
{
"store": {
"name": "TechBooks Online",
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95,
"inStock": true
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99,
"inStock": false
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99,
"inStock": true
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99,
"inStock": true
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}Practical JSONPath Examples
1. Get the Store Name
$.store.nameResult:
"TechBooks Online"2. Get All Book Titles
$.store.book[*].titleResult:
[
"Sayings of the Century",
"Sword of Honour",
"Moby Dick",
"The Lord of the Rings"
]3. Get the First Book
$.store.book[0]Result: the entire first book object.
4. Get the Last Book
$.store.book[-1]Negative indices count from the end. -1 gives you the last element.
5. Slice: First Two Books
$.store.book[0:2]Returns books at index 0 and 1. The end index is exclusive, just like in Python and JavaScript's slice().
6. Get All Prices in the Entire Document
$..priceResult:
[8.95, 12.99, 8.99, 22.99, 19.95]The recursive descent operator .. finds every pricefield regardless of depth — including the bicycle's price.
7. Filter: Books Under $10
$.store.book[?(@.price < 10)]Returns the two books priced at 8.95 and 8.99. The @ symbol refers to the current item being evaluated.
8. Filter: Books That Have an ISBN
$.store.book[?(@.isbn)]Returns only Moby Dick and The Lord of the Rings.
9. Filter: Fiction Books in Stock
$.store.book[?(@.category == 'fiction' && @.inStock == true)]You can combine conditions with && (and) and || (or).
10. Multiple Indices
$.store.book[0,3]Cherry-pick specific elements. Returns the first and fourth books.
11. Wildcard on Object
$.store.bicycle.*Result:
["red", 19.95]Returns all values of the bicycle object.
12. Get Authors of Expensive Books
$.store.book[?(@.price > 15)].authorResult:
["J. R. R. Tolkien"]Try it yourself: Paste the sample JSON above and experiment with these expressions in our JSONPath Tester.
Filter Expressions in Depth
Filter expressions are the most powerful part of JSONPath. They live inside [?()] and evaluate a boolean condition for each element. Here are the operators you can use:
| Operator | Meaning | Example |
|---|---|---|
== | Equal to | @.status == 'active' |
!= | Not equal to | @.role != 'admin' |
> | Greater than | @.age > 18 |
>= | Greater than or equal | @.score >= 90 |
< | Less than | @.price < 50 |
<= | Less than or equal | @.quantity <= 0 |
=~ | Regex match (some implementations) | @.name =~ /^J.*/ |
&& | Logical AND | @.price > 5 && @.price < 20 |
|| | Logical OR | @.status == 'active' || @.role == 'admin' |
One common pattern is checking for property existence. Writing @.isbn without a comparison operator returns true if the property exists and is not null.
Real-World Scenarios
Extract All Emails from an API Response
Suppose you get a response from a user management API with deeply nested contact objects:
{
"users": [
{
"name": "Alice",
"contacts": { "email": "alice@example.com", "phone": "555-0101" }
},
{
"name": "Bob",
"contacts": { "email": "bob@example.com", "phone": "555-0102" }
}
]
}Query: $..email
Result:
["alice@example.com", "bob@example.com"]The recursive descent operator finds every email field no matter where it sits in the hierarchy.
Find Products by Price Range
$.products[?(@.price >= 25 && @.price <= 100)]Useful when filtering API responses client-side before rendering a product list.
Get Nested Configuration Values
$.config.database.connections[0].hostDrill into configuration files without writing chained optional access like config?.database?.connections?.[0]?.host.
JSONPath in Different Languages
JavaScript / Node.js
The jsonpath-plus package is the most popular implementation:
import { JSONPath } from "jsonpath-plus";
const data = { store: { book: [{ title: "Moby Dick", price: 8.99 }] } };
const titles = JSONPath({ path: "$.store.book[*].title", json: data });
console.log(titles); // ["Moby Dick"]Python
Use jsonpath-ng for a standards-compliant implementation:
from jsonpath_ng.ext import parse
data = {"store": {"book": [{"title": "Moby Dick", "price": 8.99}]}}
expr = parse("$.store.book[*].title")
matches = [match.value for match in expr.find(data)]
print(matches) # ['Moby Dick']Java
The Jayway JSONPath library is the go-to choice for JVM projects:
import com.jayway.jsonpath.JsonPath;
String json = "{\"store\":{\"book\":[{\"title\":\"Moby Dick\",\"price\":8.99}]}}";
List<String> titles = JsonPath.read(json, "$.store.book[*].title");
System.out.println(titles); // [Moby Dick]Gotchas and Limitations
JSONPath is powerful, but there are a few things to watch out for before you rely on it in production:
- No standard specification (until recently). JSONPath was informally defined, and different libraries implemented it with subtle variations. The IETF published RFC 9535 in 2024 to standardize the syntax, but many libraries still follow the original Goessner spec. Always check your library's documentation for supported features.
- Regex support varies. The
=~regex operator is not universally supported. The Jayway Java library supports it; many JavaScript libraries do not. - No write operations. JSONPath is read-only. You cannot use it to modify the original document. For mutations, you need JSON Patch (RFC 6902) or manual traversal.
- Performance on huge documents. Recursive descent (
..) scans every node in the tree. On documents with millions of nodes, this can be slow. Prefer specific paths when performance matters. - Return type inconsistency. Some libraries return a single value for single matches and an array for multiple matches. Others always return an array. Normalize the return type in your code to avoid surprises.
- Special characters in keys. If your JSON keys contain dots, spaces, or other special characters, use bracket notation:
$['my.key']instead of$.my.key.
JSONPath vs. jq
You might wonder how JSONPath compares to jq, the popular command-line JSON processor. The short answer: they solve similar problems but for different audiences.
- JSONPath is designed for embedding in application code. It has library support in every major language and a straightforward query syntax.
- jq is a full-fledged data transformation language. It can filter, map, reduce, and reshape JSON. It is more powerful but has a steeper learning curve and is primarily used on the command line.
For querying data inside an application, JSONPath is the pragmatic choice. For shell scripting and data pipelines, jq is hard to beat.
Quick Reference Cheat Sheet
| What You Want | JSONPath |
|---|---|
| Entire document | $ |
| Specific property | $.user.name |
| All items in array | $.items[*] |
| First item | $.items[0] |
| Last item | $.items[-1] |
| Range of items | $.items[1:4] |
| Every nth item | $.items[::2] |
| All "name" fields anywhere | $..name |
| Filter by value | $.items[?(@.price < 10)] |
| Filter by existence | $.items[?(@.discount)] |
| Multiple conditions | $.items[?(@.qty > 0 && @.active)] |
Try it yourself: Open the JSONPath Tester to run queries against your own JSON data in real time.