Skip to content
LocalOnly
JSON Tools

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.

Updated October 18, 20263 min readTool runs in your browser

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].id gives ["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 uses select(...) 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] | add in 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.

Frequently asked questions

Command Palette

Search for a tool or command