DevToys Pro

free web developer tools

Blog
Rate us:
Try browser extension:
← Back to Blog

JSONPath Cheatsheet for Debugging APIs

15 min read

JSONPath is a query language for JSON, similar to XPath for XML. It allows you to extract specific data from complex JSON structures using concise path expressions. This is especially useful when debugging API responses, processing large datasets, or extracting values from nested objects.

This cheatsheet covers the essential JSONPath operators with practical examples focused on API debugging scenarios.

Quick Reference

OperatorDescriptionExample
$Root object$.user
@Current object (in filters)@.price
.Child operator$.user.name
..Recursive descent$..email
*Wildcard (all elements)$.users[*].name
[]Array subscript$.items[0]
[start:end]Array slice$.items[0:3]
[?()]Filter expression$.items[?(@.price < 10)]

Sample Data

We'll use this API response structure for examples:

{
  "status": "success",
  "user": {
    "id": 101,
    "name": "Alice Johnson",
    "email": "alice@example.com",
    "roles": ["admin", "editor"]
  },
  "products": [
    {
      "id": 1,
      "name": "Laptop",
      "price": 999,
      "inStock": true,
      "tags": ["electronics", "computers"]
    },
    {
      "id": 2,
      "name": "Mouse",
      "price": 25,
      "inStock": true,
      "tags": ["electronics", "accessories"]
    },
    {
      "id": 3,
      "name": "Monitor",
      "price": 349,
      "inStock": false,
      "tags": ["electronics", "displays"]
    }
  ],
  "metadata": {
    "timestamp": "2026-01-11T10:00:00Z",
    "version": "1.0",
    "pagination": {
      "page": 1,
      "pageSize": 10,
      "total": 45
    }
  }
}

Basic Selection

Root and Direct Access

JSONPathResult
$Entire document
$.status"success"
$.user.name"Alice Johnson"
$.user.email"alice@example.com"

Nested Object Access

JSONPathResult
$.metadata.version"1.0"
$.metadata.pagination.page1
$.metadata.pagination.total45

Array Operations

Array Indexing

JSONPathResult
$.products[0]First product (Laptop)
$.products[1].name"Mouse"
$.products[-1]Last product (Monitor)
$.user.roles[0]"admin"

Array Slicing

JSONPathResult
$.products[0:2]First two products
$.products[1:]All products except first
$.products[:2]First two products
$.products[-2:]Last two products

Wildcards

JSONPathResult
$.products[*].nameAll product names
$.products[*].priceAll product prices: [999, 25, 349]
$.products[*].idAll product IDs: [1, 2, 3]

Deep Scanning (Recursive Descent)

The .. operator searches the entire document tree:

JSONPathResult
$..nameAll "name" fields at any level
$..priceAll "price" fields: [999, 25, 349]
$..idAll "id" fields: [101, 1, 2, 3]
$..tags[0]First tag of all products

Filter Expressions

Filter expressions use [?()] syntax with @ referring to the current item:

Comparison Operators

JSONPathResult
$.products[?(@.price < 100)]Products under $100 (Mouse)
$.products[?(@.price >= 300)]Products $300+ (Laptop, Monitor)
$.products[?(@.inStock == true)]In-stock products (Laptop, Mouse)
$.products[?(@.inStock != true)]Out-of-stock products (Monitor)

Logical Operators

JSONPathResult
$.products[?(@.price < 100 && @.inStock)]Cheap + in stock (Mouse)
$.products[?(@.price > 500 || !@.inStock)]Expensive or out of stock

Existence Checks

JSONPathResult
$.products[?(@.tags)]Products with tags field
$..products[?(@.discount)]Products with discount field (none)

Real-World API Debugging Scenarios

Scenario 1: Extract All Error Messages

{
  "results": [
    {"status": "success", "data": {...}},
    {"status": "error", "message": "Invalid token"},
    {"status": "success", "data": {...}},
    {"status": "error", "message": "Database timeout"}
  ]
}

// Extract all error messages
$.results[?(@.status == 'error')].message

// Result: ["Invalid token", "Database timeout"]

Scenario 2: Find Items with Specific Tags

// Find all products tagged "electronics"
$.products[?(@.tags[*] == 'electronics')]

// Find products with BOTH "electronics" AND "computers" tags
$.products[?(@.tags[*] == 'electronics' && @.tags[*] == 'computers')]

Scenario 3: Extract Nested Pagination Data

{
  "response": {
    "data": [...],
    "meta": {
      "pagination": {
        "current_page": 2,
        "total_pages": 10,
        "next_page": 3
      }
    }
  }
}

// Extract pagination info
$.response.meta.pagination
// or use deep scan
$..pagination

Scenario 4: Filter Multi-Level Data

{
  "users": [
    {
      "name": "Alice",
      "orders": [
        {"id": 1, "total": 100, "paid": true},
        {"id": 2, "total": 50, "paid": false}
      ]
    },
    {
      "name": "Bob",
      "orders": [
        {"id": 3, "total": 200, "paid": true}
      ]
    }
  ]
}

// Get all unpaid orders (any user)
$..orders[?(@.paid == false)]

// Get users with unpaid orders
$.users[?(@.orders[?(@.paid == false)])]

Scenario 5: Extract IDs for Bulk Operations

// Get IDs of all in-stock products under $100
$.products[?(@.inStock && @.price < 100)].id

// Get IDs of all products
$.products[*].id

// Get IDs using deep scan (works even if structure changes)
$..products[*].id

Advanced Patterns

Combining Multiple Filters

// Products: in stock, under $500, with "electronics" tag
$.products[?(
  @.inStock == true &&
  @.price < 500 &&
  @.tags[*] == 'electronics'
)]

Regex Matching (Implementation-Dependent)

// Products where name starts with "M"
$.products[?(@.name =~ /^M/)]

// Users with .com email addresses
$.users[?(@.email =~ /\.com$/)]

Note: Regex support varies by JSONPath implementation.

Script Expressions (Advanced)

// Products with price within 10% of average
$.products[?(@.price < 400)]

// Calculate length
$.products[?(@.tags.length > 1)]

Common Pitfalls

Pitfall 1: Forgetting the Root

// Wrong - no root
products[0].name

// Correct
$.products[0].name

Pitfall 2: Confusing . and ..

// Single dot - direct child
$.user.name  // Only gets user.name

// Double dot - recursive search
$..name  // Gets ALL "name" fields at any level

Pitfall 3: Wildcard vs Filter

// Wildcard - gets all elements
$.products[*].name

// Filter - gets elements matching condition
$.products[?(@.inStock)].name

Pitfall 4: Array Index Out of Bounds

// If array has 3 items:
$.products[10]  // Returns undefined/null, not error

// Use filters or wildcards for safety
$.products[?(@.id == 10)]

Implementation Differences

JSONPath implementations vary across languages and libraries:

JavaScript (jsonpath library)

const jp = require('jsonpath');

const result = jp.query(data, '$.products[?(@.price < 100)]');
console.log(result);

Python (jsonpath-ng)

from jsonpath_ng import parse

expression = parse('$.products[*].name')
matches = [match.value for match in expression.find(data)]

Go (gjson)

import "github.com/tidwall/gjson"

result := gjson.Get(jsonString, "products.#(price<100).name")
fmt.Println(result.String())

Testing JSONPath Expressions

Use the DevToys Pro JSONPath Tester to:

  • Test expressions against real API responses
  • Debug complex filter conditions
  • Validate path syntax before implementing in code
  • Experiment with different operators and combinations
  • Compare results across different query approaches

Best Practices

1. Start Simple, Then Refine

// Step 1: Get all products
$.products

// Step 2: Get all product names
$.products[*].name

// Step 3: Filter by condition
$.products[?(@.inStock)].name

2. Use Filters Over Complex Indexing

// Less maintainable - assumes order
$.products[2].name

// More maintainable - finds by property
$.products[?(@.id == 3)].name

3. Deep Scan Sparingly

// Can be slow on large documents
$..price

// Prefer specific paths when possible
$.products[*].price

4. Handle Missing Data

// Check existence before accessing
$.user.profile?.address?.city

// Or use existence filter
$.users[?(@.profile && @.profile.address)]

Conclusion

JSONPath provides a powerful, concise syntax for querying JSON structures. Key takeaways:

  • Use $ for root, . for child access, .. for recursive search
  • Array operations support indexing, slicing, and wildcards ([0], [0:3], [*])
  • Filter expressions [?()] enable conditional selection
  • Combine operators for complex queries: $.products[?(@.price < 100)].name
  • Test expressions before deploying to production
  • Be aware of implementation differences across languages

For testing and debugging JSONPath expressions, use the JSONPath Tester to validate queries against real API responses and experiment with different selection strategies.