JSONPath vs jq vs JMESPath: Syntax Compared with Examples
Three query languages for JSON look alike and behave differently. Here are ten everyday queries in all three, with the results they actually return.
JSONPath, jq and JMESPath all pull values out of JSON, and simple queries look almost the same in each. The differences show up in filters, in the shape of the result and in what happens when you want to count or reshape data. The JSONPath Evaluator lets you test JSONPath expressions in the browser.
This guide puts the three side by side. Every query was run on the same small document with an RFC 9535 JSONPath implementation, jq and the JMESPath reference implementation.
The document
{"orders": [
{"id": "ord_1", "status": "paid", "total": 120.5,
"customer": {"name": "Ana", "country": "DE"},
"items": [{"sku": "A-1", "qty": 2}, {"sku": "B-7", "qty": 1}]},
{"id": "ord_2", "status": "refunded", "total": 35,
"customer": {"name": "José", "country": "ES"},
"items": [{"sku": "C-3", "qty": 1}]},
{"id": "ord_3", "status": "paid", "total": 310,
"customer": {"name": "Priya", "country": "IN"},
"items": [{"sku": "A-1", "qty": 5}]}
]}JSONPath, jq and JMESPath examples side by side
Task JSONPath jq JMESPath
One field $.orders[0].id .orders[0].id orders[0].id
All ids $.orders[*].id [.orders[].id] orders[*].id
Last item $.orders[-1].id .orders[-1].id orders[-1].id
First two $.orders[0:2].id [.orders[:2][].id] orders[:2].id
Filter by text $.orders[?@.status == 'paid'].id [.orders[] | select(.status == "paid") | .id] orders[?status == 'paid'].id
Filter number $.orders[?@.total > 100].id [.orders[] | select(.total > 100) | .id] orders[?total > `100`].id
Nested field $.orders[*].customer.country [.orders[].customer.country] orders[*].customer.country
Any depth $..sku [.. | .sku? // empty] orders[].items[].sku
Count (not possible) .orders | length length(orders)
Reshape (not possible) [.orders[] | {id, country: .customer.country}] orders[].{id: id, country: customer.country}Wherever a language can express the query, all three returned the same data: ["ord_1", "ord_3"] for both filters, for example, and ["A-1", "B-7", "C-3", "A-1"] for the SKUs at any depth.
The differences that matter
- Result shape. JSONPath always returns a list of matches, even for a single field:
$.orders[0].idgives["ord_1"]. jq and JMESPath return the value itself,"ord_1". In jq, a query over many items produces a stream of separate values unless you wrap it in brackets, which is why the table uses[...]. - Filters. JSONPath and JMESPath use
[?...]with single-quoted strings. jq usesselect(...)inside a pipeline with double-quoted strings. JMESPath wraps number literals in backticks, as the table shows; without them, the reference implementation stops with a parse error. - Searching at any depth. JSONPath has
..and jq has..combined with?to skip values that are not objects. JMESPath has no recursive search, so you spell out the path. - Counting, summing and reshaping. JSONPath only selects; it cannot compute or build new objects. jq and JMESPath both can, so
sum(orders[].total)in JMESPath and[.orders[].total] | addin jq both return 465.5.
Not every JSONPath is the same JSONPath
JSONPath existed for years as an informal article before RFC 9535 standardised it in 2024, and older libraries still differ in the details. Filter syntax is the usual example: RFC 9535 writes [?@.status == 'paid'], while many older implementations expect parentheses, [?(@.status == 'paid')]. When an expression works in one tool and fails in another, check which variant each one implements.
Choosing one
- jq for the command line and scripts. It is the most capable of the three, a full language with variables, functions and arithmetic.
- JMESPath when a tool or library already offers it, or when you want a small, well-specified language with the same result in every implementation.
- JSONPath for selecting values inside an application, such as picking fields from an API response or pointing at a location in a document, where you do not need to compute anything.
Related tools
Explore JSON as a collapsible, interactive tree.
Flatten nested JSON into single-level dot-notation key paths.
Pretty-print JSON with configurable indentation and instant validation.
Validate JSON against a JSON Schema using AJV.
Related guides
Loading a 121 MB JSON file took over a gigabyte of memory in jq. Streaming the same file took 3 MB. Here is how to explore large JSON without loading it all at once.
A recursive function of about ten lines flattens any JSON document into path keys. Here it is for JavaScript and Python, with the choices that decide whether you can rebuild the original.
Two formatters can disagree about the same JSON file. Here is how a team picks one, applies it in every editor and before every commit, and makes CI enforce it.
Checking a request body against a JSON Schema replaces dozens of hand-written if statements. Here is a tested setup for Node and Python, and a 400 response that tells the client exactly what to fix.