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.
| Path | Result |
|---|---|
shop | "Hind Books" |
orders[0].id | "A-1001" |
orders[0].customer.name | "Asha" |
orders[1].items[0].sku | "BK-2" |
orders[0].items | The array with two items |
orders[5].id | Nothing: 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.jsonJSONPath
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.
| JSONPath | Meaning |
|---|---|
$.shop | The shop key at the root |
$.orders[0].id | The id of the first order |
$.orders[*].id | The id of every order (* is a wildcard) |
$..sku | Every sku anywhere in the document (.. means search at any depth) |
$.orders[?@.total > 100].id | Ids 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 forNonebefore going deeper. - In jq, the
?suffix suppresses errors for a step (.orders[]?.customer?.email), and// "default"supplies a fallback value. - Remember that
nulland "key is absent" are different; the second order above has"email": null, which is present but empty.
A practical workflow
- Format the document so you can see its real structure.
- Find one example of the value you want and note its path.
- Try the path on a single record, then widen it with a wildcard or a filter.
- 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.