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.

JSONTech TeamFebruary 1, 202511 min read

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:

ConceptXPathJSONPath
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:

OperatorDescriptionExample
$The root object or array$ — the entire document
.keyChild 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.name

Result:

"TechBooks Online"

2. Get All Book Titles

$.store.book[*].title

Result:

[
  "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

$..price

Result:

[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)].author

Result:

["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:

OperatorMeaningExample
==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].host

Drill 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 WantJSONPath
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.

Related Tools