JSONPath vs jq vs JMESPath

$.store.book[[email protected] < 10].title

$.store.book[[email protected] < 10].title is the JSONPath for the titles of the books under 10. In jq it’s .store.book[] | select(.price < 10) | .title, and in JMESPath it’s store.book[?price < `10`].title. All three answer Sayings of the Century and Moby Dick on the RFC 9535 bookstore, the document in the tester below.

Five queries in each

Run with the tester’s RFC 9535 core, jq 1.7.1 and the jmespath Python package 1.1.0:

one value      JSONPath   $.store.bicycle.color
               jq         .store.bicycle.color
               JMESPath   store.bicycle.color

every title    JSONPath   $.store.book[*].title
               jq         .store.book[].title
               JMESPath   store.book[*].title

a filter       JSONPath   $.store.book[[email protected] < 10].title
               jq         .store.book[] | select(.price < 10) | .title
               JMESPath   store.book[?price < `10`].title

the last one   JSONPath   $.store.book[-1].title
               jq         .store.book[-1].title
               JMESPath   store.book[-1].title

at any depth   JSONPath   $..price
               jq         [.. | objects | select(has("price")) | .price]
               JMESPath   (none)

$..price and the jq line both give 8.95, 12.99, 8.99, 22.99, 399. JMESPath has no recursive descent, so store..price is a parse error and each level has to be written out.

Where they differ

  • What comes back. JSONPath selects nodes, each with a normalized path such as $['store']['book'][2]['title']. jq yields a stream: "Sayings of the Century" and "Moby Dick" print as two separate values, and [ … ] around the filter collects them into an array. JMESPath returns one JSON value, here ["Sayings of the Century", "Moby Dick"].
  • Missing members. $.store.book[0].isbn selects nothing. jq and JMESPath both return null, the same as for a member that holds null.
  • Comparing across types. JSONPath’s @.price < "10" is false for every book. jq orders all values, numbers before strings, so select(.price < "10") keeps all four. The JMESPath spec says < on anything but numbers yields null, which drops the element; the Python package raises a TypeError instead.
  • Literals. JMESPath writes JSON literals in backticks and raw strings in single quotes. price < 10 without backticks is a parse error in the Python package.
  • Reshaping. jq and JMESPath build new objects, {title, price} and {title: title, price: price}. JSONPath only selects; it can’t rename, compute or construct.

Which one you’ll meet

  • JSONPath has been an IETF standard since February 2024 (RFC 9535); libraries such as Jayway JsonPath for Java and jsonpath-plus for JavaScript are older and differ from it in places. kubectl -o jsonpath takes Kubernetes’ own template form, in braces: {.items[*].metadata.name}.
  • jq is a command-line program and a full language, with variables, functions and arithmetic. gh --jq takes its syntax.
  • JMESPath is what aws --query and az --query take, with a written specification at jmespath.org.

Try it in the JSONPath Tester

Member names, wildcards, indexes, slices, descendants and filters, the way RFC 9535 defines them. match() and search() take I-Regexp patterns: . is any character but \n and \r, \p{Lu} is a Unicode category, and there are no \d classes, no backreferences and no anchors.

Selection

2 values selected

PathValue
$['store']['book'][0]['title']"Sayings of the Century"
$['store']['book'][2]['title']"Moby Dick"

The document, with the selection marked

{
  "store": {
    "book": [
      { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 },
      { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 },
      { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 },
      { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 }
    ],
    "bicycle": { "color": "red", "price": 399 }
  }
}

Runs in your browser. Nothing you enter is sent anywhere.