Skip to the content
GigaJSON
All tools
Guides Open GigaJSON

jq vs JSONPath vs JMESPath: the same 8 tasks in all three

· 6 min read

All three languages answer "get me these values out of this JSON". They differ in how much further they go. The quickest way to see that is to solve the same problems in each, so that's what this post does: eight tasks, one data file, every query tested (jq 1.7, the jsonpath-plus library for JSONPath, and jmespath.js).

You can paste the sample into the JSONPath tester or the jq playground and switch languages to try each line.

The short answer

  • Use JSONPath when a tool asks for it, or when all you need is to point at values: test assertions, config templates, kubectl -o jsonpath.
  • Use JMESPath when you're in the AWS or Azure CLI (--query), or when you want selection plus a bit of reshaping with a strict, portable spec.
  • Use jq for anything with computation or reshaping, and for shell scripts. It's the only one of the three that is a programming language.

If you're learning one from scratch for your own use, learn jq.

The sample data

{
  "orders": [
    { "id": "A-100", "customer": { "name": "Ana", "country": "PT" }, "status": "paid",
      "total": 120.5, "items": [ { "sku": "KB-01", "qty": 1 }, { "sku": "MS-02", "qty": 2 } ] },
    { "id": "A-101", "customer": { "name": "Ben", "country": "US" }, "status": "pending",
      "total": 35, "items": [ { "sku": "MS-02", "qty": 1 } ] },
    { "id": "A-102", "customer": { "name": "Chloe", "country": "PT" }, "status": "paid",
      "total": 410, "items": [ { "sku": "MN-27", "qty": 1 }, { "sku": "KB-01", "qty": 1 } ] }
  ]
}

Task 1: every customer name

Language Query Result
JSONPath $.orders[*].customer.name ["Ana","Ben","Chloe"]
JMESPath orders[*].customer.name ["Ana","Ben","Chloe"]
jq [.orders[].customer.name] ["Ana","Ben","Chloe"]

Nearly identical. Two differences already show. JSONPath always starts at $ and always returns a list of matches. jq returns a stream of separate values; without the outer brackets you'd get three lines, "Ana", "Ben" and "Chloe", which is exactly what you want in a shell loop.

Task 2: orders over 100

Language Query
JSONPath $.orders[?(@.total > 100)].id
JMESPath orders[?total > `100`].id
jq [.orders[] | select(.total > 100) | .id]

All return ["A-100","A-102"]. JMESPath writes literals in backticks (`100`, `"x"`) and raw strings in single quotes ('paid'). That trips people up, especially in shell scripts where backticks mean something else; wrap the whole query in single quotes.

RFC 9535, the JSONPath standard since 2024, allows the parentheses to be dropped: $.orders[?@.total > 100].id. Many libraries still expect the older ?(...) form (jsonpath-plus returns [] without the parentheses), and that's the form that works almost everywhere.

Task 3: every SKU from nested arrays

Language Query
JSONPath $.orders[*].items[*].sku or $..sku
JMESPath orders[].items[].sku
jq [.orders[].items[].sku]

Result: ["KB-01","MS-02","MS-02","MN-27","KB-01"].

JSONPath's .. (recursive descent) finds a key at any depth, which is handy when you don't know the structure. jq's equivalent is [.. | .sku? // empty].

The JMESPath version has a trap. orders[*].items[*].sku keeps the nesting and returns [["KB-01","MS-02"],["MS-02"],["MN-27","KB-01"]]. [] flattens one level; [*] projects without flattening. Use [] when you want one flat list.

Task 4: count the orders

Language Query Result
JSONPath none in the standard
JMESPath length(orders) 3
jq .orders | length 3

This is where JSONPath stops. RFC 9535 has a length() function, but only inside filter expressions, not as a result. Some libraries return the JavaScript .length property for $.orders.length (jsonpath-plus gives [3]), which is a side effect of the implementation, not something to rely on.

Task 5: two conditions at once

Paid orders from Portugal:

Language Query
JSONPath $.orders[?(@.customer.country == "PT" && @.status == "paid")].id
JMESPath orders[?customer.country == 'PT' && status == 'paid'].id
jq [.orders[] | select(.customer.country == "PT" and .status == "paid") | .id]

All return ["A-100","A-102"]. JSONPath and JMESPath use && and ||; jq uses the words and and or.

Task 6: orders that contain a given SKU

A filter on a nested array: which orders include KB-01?

Language Query
JSONPath (RFC 9535) $.orders[?@.items[?@.sku == 'KB-01']].id
JMESPath orders[?contains(items[].sku, `"KB-01"`)].id
jq [.orders[] | select(any(.items[]; .sku == "KB-01")) | .id]

JMESPath and jq both return ["A-100","A-102"]. The JSONPath query is valid RFC 9535 syntax (a nested filter used as an existence test), but support is uneven: jsonpath-plus returned [] for it. If your JSONPath library predates the RFC, this kind of query is the first thing to break, and the usual answer is to select the orders and filter them in code.

Task 7: build new objects

Just the id and total of each order:

Language Query
JSONPath not possible
JMESPath orders[*].{id: id, total: total}
jq [.orders[] | {id, total}]

Both give [{"id":"A-100","total":120.5},{"id":"A-101","total":35},{"id":"A-102","total":410}]. jq's {id, total} is shorthand for {id: .id, total: .total}.

JSONPath can't create anything: it returns existing values. $.orders[*]['id','total'] returns ["A-100",120.5,"A-101",35,...], a flat list with the pairing lost.

Task 8: sort, sum and find the maximum

What JMESPath jq
ids sorted by total sort_by(orders, &total)[*].id .orders | sort_by(.total) | map(.id)
sum of totals sum(orders[*].total) [.orders[].total] | add
id of the largest order max_by(orders, &total).id .orders | max_by(.total) | .id

Results: ["A-101","A-100","A-102"], 565.5, "A-102". JSONPath has no sorting or arithmetic at all.

The & in JMESPath is an expression reference: it passes total as "the thing to sort by" rather than evaluating it.

Beyond the eight: what only jq does

Grouping and aggregation. Total quantity per SKU across all orders:

jq '[.orders[].items[]]
    | group_by(.sku)
    | map({sku: .[0].sku, qty: (map(.qty) | add)})' orders.json
[{"sku":"KB-01","qty":2},{"sku":"MN-27","qty":1},{"sku":"MS-02","qty":3}]

JMESPath has no group-by. jq also has variables (as $x), reduce, string interpolation, regular expressions, @csv/@tsv output, reading several files, and --stream for files bigger than memory. That's why jq ends up in shell scripts and CI pipelines, and why its syntax takes longer to learn.

Side by side

JSONPath JMESPath jq
Specification RFC 9535 (2024); older libraries vary Formal spec with compliance tests The jq manual; one main implementation plus ports
Selects values Yes Yes Yes
Recursive search .. No ..
Builds new objects No Yes (multiselect hash) Yes
Sort, sum, min, max No Yes Yes
Group by, variables, reduce No No Yes
Typical home Test tools, Java/JS libraries, kubectl -o jsonpath aws --query, az --query, embedded in apps Shell, CI, data cleanup

Practical advice

Pick by where the query runs, not by which language is "best". If the AWS CLI is the place, it's JMESPath whether you like it or not. If a test framework takes JSONPath, write JSONPath and keep logic in code. If you're at a terminal with a file, jq covers every case above and many more.

When you're stuck on a query, try it against your real data rather than a toy example. Paths that work on three records often meet a null, a missing key or a string where a number was expected at record 40,000. JMESPath and JSONPath skip missing values quietly (orders[*].customer.nickname gives []), while jq gives null ([null,null,null]), so the same mistake looks different in each. For jq on log files, where one bad line can stop a whole run, see querying API responses and NDJSON logs.

The JSONPath tester and jq playground run all three languages in the browser, on data that stays on your machine. In the GigaJSON app, the Query tab does the same on documents of hundreds of megabytes with JSONPath and JMESPath. jq is limited to about 20 MB of input there, because the WebAssembly build of jq aborts past roughly 25 MB; for bigger files, run jq on the command line.

Questions

Which is more powerful, jq or JSONPath?

jq. It is a full programming language with variables, functions, reduce and string formatting, so it can reshape, group and compute. JSONPath only selects values; anything beyond selection happens in your own code.

Why does the AWS CLI use JMESPath instead of jq?

JMESPath has a precise specification with a compliance test suite, so the same query gives the same result in every implementation, and it is small enough to embed in a CLI. AWS CLI and Azure CLI both accept JMESPath in their --query option.

Is there a JSONPath standard?

Yes, since February 2024: RFC 9535. Older libraries predate it and differ in details such as filter syntax, nested filters and what .length means, so check which behaviour your library follows.

Can I test jq, JSONPath and JMESPath queries online?

Yes. GigaJSON's JSONPath tester and jq playground run all three languages in your browser on data you paste or open, without uploading it.

More from the blog