JSONPath is a practical way to extract values from nested JSON without writing repetitive traversal code. It is useful for APIs, test automation, config inspection, and data transformation, but many developers struggle with filters, wildcards, and array selection. This guide explains JSONPath syntax with real examples, shows common extraction patterns, and helps you choose when JSONPath is the right tool versus direct parsing.

Simple cases can use dot notation (data.user.email) or JavaScript destructuring. But JSONPath goes further it supports wildcards, recursive descent, array slicing, and filter expressions that are impossible with plain property access. It's also language-independent: the same JSONPath expression works in Python, JavaScript, Java, and Go.

Basic Syntax

ExpressionDescriptionExample Match
$Root elementThe entire JSON document
.Child operator$.store.book
..Recursive descent$..author all 'author' fields at any depth
*Wildcard$.store.* all children of 'store'
[]Array subscript$.store.book[0] first book
[,]Union operator$.store.book[0,2] first and third books
[start:end]Array slice$.store.book[0:2] first two books
?()Filter expression$.store.book[?(@.price < 10)]
@)Current element (in filters)@.price refers to the current item's price

Practical Example: API Response

{
  "users": {
    "total": 3,
    "data": [
      {
        "id": 1,
        "name": "Alice Chen",
        "email": "[email protected]",
        "orders": [
          { "id": 101, "total": 45.99, "status": "shipped" },
          { "id": 102, "total": 12.50, "status": "pending" }
        ]
      },
      {
        "id": 2,
        "name": "Bob Smith",
        "email": "[email protected]",
        "orders": [
          { "id": 201, "total": 150.00, "status": "delivered" }
        ]
      },
      {
        "id": 3,
        "name": "Carol Wu",
        "email": "[email protected]",
        "orders": []
      }
    ]
  }
}

Common Queries Against This Data

What you wantJSONPath expressionResult
All user names$.users.data[*].name["Alice Chen","Bob Smith","Carol Wu"]
Alice's email$.users.data[0].email"[email protected]"
All order totals$.users.data[*].orders[*].total[45.99, 12.50, 150.00]
Shipped orders$.users.data[*].orders[?(@.status=='shipped')][{"id":101,"total":45.99,"status":"shipped"}]
Users with orders$.users.data[?(@.orders.length > 0)]Alice and Bob's full objects
Total user count$.users.total3

Using JSONPath in JavaScript

// Using jsonpath-plus library (npm install jsonpath-plus)
import { JSONPath } from 'jsonpath-plus';

const data = { /* ... API response above ... */ };

// All user names
const names = JSONPath({ path: '$.users.data[*].name', json: data });
console.log(names); // ["Alice Chen", "Bob Smith", "Carol Wu"]

// Shipped orders
const shipped = JSONPath({
  path: '$.users.data[*].orders[?(@.status=="shipped")]',
  json: data
});
console.log(shipped);

// Users who have placed orders
const activeUsers = JSONPath({
  path: '$.users.data[?(@.orders.length > 0)]',
  json: data
});

Using JSONPath in Python

# pip install jsonpath-ng
from jsonpath_ng import parse

import json

with open('api_response.json') as f:
    data = json.load(f)

# All user names
names = [match.value for match in parse('$.users.data[*].name').find(data)]
print(names)  # ['Alice Chen', 'Bob Smith', 'Carol Wu']

# Shipped orders
shipped = [match.value for match in
    parse('$..orders[?(@.status=="shipped")]').find(data)]
print(shipped)

# Filter: orders over $50
expensive = [match.value for match in
    parse('$..orders[?(@.total > 50)]').find(data)]
print(expensive)  # [{"id": 201, "total": 150.00, ...}]

Advanced: Recursive Descent

// Find ALL 'email' fields regardless of nesting level
const emails = JSONPath({ path: '$..email', json: data });
// ["[email protected]", "[email protected]", "[email protected]"]

// Find all 'id' fields (matches users.id AND orders.id)
const allIds = JSONPath({ path: '$..id', json: data });
// [1, 2, 3, 101, 102, 201]

// Find all numeric values over 100
const bigNumbers = JSONPath({
  path: '$..[?(@ > 100)]',
  json: data
});

Common Patterns for API Testing

JSONPath is invaluable for API testing. Instead of writing brittle code that traverses nested objects step by step, you can express assertions as JSONPath expressions. Test frameworks like Postman, REST Assured, and pytest support JSONPath natively. Common patterns include: extracting a pagination token, verifying an error code deep in a response, or checking that every item in an array meets a condition.

// In Postman Tests tab:
const jsonData = pm.response.json();

// Verify first user's name
pm.expect(pm.jsonData.users.data[0].name).to.eql("Alice Chen");

// Using JSONPath (with postman-jsonpath):
pm expect(JSONPath("$..email", jsonData)).to.include("[email protected]");

// Check all orders have a status
const statuses = JSONPath("$..orders[*].status", jsonData);
statuses.forEach(status => pm.expect(status).to.be.oneOf(["pending", "shipped", "delivered"]));

Key Takeaways

  • JSONPath uses $ for root, . for child, .. for recursive descent
  • Array access: [0] for index, [*] for all items, [?()] for filters
  • Filters use @ to reference the current element: [?(@.price > 10)]
  • Recursive descent ($..field) finds matching fields at any nesting depth
  • In JavaScript, use jsonpath-plus; in Python, use jsonpath-ng
  • JSONPath is supported natively in Postman, REST Assured, and many API testing tools

Frequently Asked Questions

What is JSONPath used for?

JSONPath is used to select and extract specific values from nested JSON documents using a concise query-like syntax.

What is the difference between JSONPath and XPath?

JSONPath is designed for JSON structures, while XPath is designed for XML documents and follows a different query model.

Why is my JSONPath expression returning no results?

The usual causes are wrong root assumptions, incorrect array indexing, or syntax differences between JSONPath implementations.