How to Filter and Query JSON: Dot Notation, jq and JSONPath

Basics 8 min readUpdated October 12, 2026

API responses and exported files are often thousands of lines long, and you usually care about a few fields. A path expression lets you say exactly where a value lives, such as orders[0].customer.email, and get just that. This guide covers the simple dot notation most tools understand, then the two common query languages for more power: jq and JSONPath.

A sample document

The examples below use this order data.

{
  "shop": "Hind Books",
  "orders": [
    {
      "id": "A-1001",
      "customer": { "name": "Asha", "email": "asha@example.com" },
      "total": 249.5,
      "items": [
        { "sku": "BK-1", "qty": 2 },
        { "sku": "BK-7", "qty": 1 }
      ]
    },
    {
      "id": "A-1002",
      "customer": { "name": "Rohan", "email": null },
      "total": 80,
      "items": [ { "sku": "BK-2", "qty": 1 } ]
    }
  ]
}

Dot notation and array indexes

Dot notation follows keys downward, separated by dots. Square brackets with a number pick an item from an array, counting from zero.

PathResult
shop"Hind Books"
orders[0].id"A-1001"
orders[0].customer.name"Asha"
orders[1].items[0].sku"BK-2"
orders[0].itemsThe array with two items
orders[5].idNothing: index 5 does not exist

A key that contains a dot, space or other special character cannot be written as plain dot notation. Most tools use bracket syntax with quotes for it, such as ["first name"]. Check how your tool treats such keys before relying on it.

Query the whole array with jq

Plain paths pick one location. To work across every element, such as "the id of every order" or "orders over 100", use a query language. jq is the most widely used command-line tool for this.

# One value
jq '.orders[0].customer.name' orders.json

# The id of every order
jq '.orders[].id' orders.json

# Only orders with a total above 100
jq '.orders[] | select(.total > 100) | .id' orders.json

# Build a new, smaller object for each order
jq '.orders[] | { id, email: .customer.email, itemCount: (.items | length) }' orders.json

# All SKUs from all orders (flatten the nested arrays)
jq '[.orders[].items[].sku] | unique' orders.json
Add -r to print strings without quotes

JSONPath

JSONPath is a query syntax modelled on XPath, with the root written as $. It was used for years without a formal standard, and it was published as RFC 9535 in 2024. Libraries exist for most languages, and implementations written before the RFC may differ in edge cases.

JSONPathMeaning
$.shopThe shop key at the root
$.orders[0].idThe id of the first order
$.orders[*].idThe id of every order (* is a wildcard)
$..skuEvery sku anywhere in the document (.. means search at any depth)
$.orders[?@.total > 100].idIds of orders whose total is above 100 (a filter)
$.orders[-1]The last order

The same thing in JavaScript and Python

const data = JSON.parse(text);

// Optional chaining stops at the first missing step instead of throwing
const email = data.orders?.[1]?.customer?.email ?? "no email";

// Filter and map arrays with the standard methods
const bigOrderIds = data.orders.filter(o => o.total > 100).map(o => o.id);

// A tiny path helper for strings like "orders[0].customer.name"
function get(obj, path) {
  return path
    .replace(/\[(\d+)\]/g, ".$1")
    .split(".")
    .filter(Boolean)
    .reduce((value, key) => (value == null ? undefined : value[key]), obj);
}
console.log(get(data, "orders[0].customer.name")); // "Asha"
import json

data = json.load(open("orders.json", encoding="utf-8"))

big_order_ids = [o["id"] for o in data["orders"] if o["total"] > 100]

# .get() avoids a KeyError when a key is missing
email = data["orders"][1]["customer"].get("email") or "no email"

Handle missing keys and null

Real data is untidy. Some records lack a key, some have null where you expected an object. Decide what each case should mean and write for it.

  • In JavaScript, use optional chaining (?.) and the nullish coalescing operator (??) to supply a default.
  • In Python, use dict.get() and check for None before going deeper.
  • In jq, the ? suffix suppresses errors for a step (.orders[]?.customer?.email), and // "default" supplies a fallback value.
  • Remember that null and "key is absent" are different; the second order above has "email": null, which is present but empty.

A practical workflow

  1. Format the document so you can see its real structure.
  2. Find one example of the value you want and note its path.
  3. Try the path on a single record, then widen it with a wildcard or a filter.
  4. Check the count of results against what you expect before using them.

A filter box inside an editor, where you type a path and see the matching part highlighted straight away, makes the middle steps much quicker than editing a command and re-running it.

Frequently asked questions

What is the difference between JSONPath and jq?

JSONPath is a compact syntax for selecting values, with libraries in many languages and a standard (RFC 9535). jq is a complete command-line language that can also transform, compute and restructure data. For simple selection either works; for reshaping data, jq is more powerful.

Do array indexes start at 0 or 1?

At 0 in JSON tools, JavaScript, Python, jq and JSONPath. The first element is [0].

How do I get all values for a key at any depth?

In JSONPath use the recursive descent operator, $..key. In jq use .. | .key? | select(. != null). In code, write a small recursive function that walks objects and arrays.

Can I filter without installing anything?

Yes. A browser-based editor with a filter field lets you type a dot-notation path and see the result immediately, which is handy for exploring unfamiliar data.

Related guides

See all 12 guides

© 2026 TrueJson. All rights reserved.