skip to content

In a Karate feature file, what is the difference between `get cat.kittens[*].id`, the `$cat.kittens[*].id` short-cut, and `karate.jsonPath(cat, expression)` — and when do you need the last one?

level: middleimportance: should knowfreq 56%

answer

  1. Two spellings, same evaluation
  2. Only one of them takes an index
  3. The path is a literal substring
  4. A runtime value needs the JS form
  5. That form takes the path as an argument

basics

~20 s

get and the $ short-cut both apply a JsonPath, written as literal step text, to a named variable. karate.jsonPath takes the path as a string argument, so use it when the path is built at runtime.

solid answer

~40 s

`get cat.kittens[*].id` and `$cat.kittens[*].id` both mean "apply this JsonPath to the variable `cat`", and are interchangeable except that only `get` takes an index (`get[0] ...`). Both take the path from the **step text itself** — it is a literal substring of the line, so you cannot splice a variable into it. Karate's own docs say so directly about a named path on the left of `match`: it cannot be dynamic, use an extra step if you need that. `karate.jsonPath(json, expression)` is the escape hatch, because there the path is an ordinary string argument you can build in JavaScript: `karate.jsonPath(cat, "$.kittens[?(@.name=='" + bob.name + "')]")`. Reach for it when the path — usually a filter value — comes from a variable; otherwise prefer the step-text forms, which read better and fail more clearly.

code

gherkin · 29 lines
gherkin
* def cat =
  """
  {
    name: 'Billie',
    kittens: [
      { id: 23, name: 'Bob' },
      { id: 42, name: 'Wild' }
    ]
  }
  """

# get and the $ short-cut are interchangeable
* def viaGet = get cat.kittens[*].id
* def viaDollar = $cat.kittens[*].id
* match viaGet == [23, 42]
* match viaDollar == [23, 42]

# the two-token form takes a full JsonPath
* def kitnames = get cat $.kittens[*].name
* match kitnames == ['Bob', 'Wild']

# only get takes an index
* def bob = get[0] cat.kittens[?(@.id == 23)]
* match bob.name == 'Bob'

# the path here is literal - you cannot splice a variable into it
* def wanted = 'Bob'
* def found = karate.jsonPath(cat, "$.kittens[?(@.name=='" + wanted + "')]")[0]
* match found == bob

go deeper

for a junior

Recall that get and $ are two spellings of the same selection, and that get is written after an equals sign or on the right of a match, never at the start of a step or of a match left side.

for a middle

Explain that the path is literal step text with no interpolation, and that karate.jsonPath exists precisely because its path is a string argument you can build.

for a senior

Prefer keeping paths static and doing the narrowing in an earlier step; a feature file full of concatenated filter strings is hard to review and quotes are easy to get subtly wrong.

for a principal

This is the boundary between a declarative assertion language and an embedded scripting language. Set the team's line on how much logic may cross it before a test stops being readable by a non-author.

## Two spellings, one evaluation A step like `* def kitnums = get cat.kittens[*].id` and `* def kitnums = $cat.kittens[*].id` do the same work. Both take the leading identifier as the **variable** and everything after it as a **JsonPath** applied to that variable's value. The equivalences are worth memorising: | Written as | Means | |---|---| | `get cat.kittens[*].id` | JsonPath `$.kittens[*].id` on the variable `cat` | | `$cat.kittens[*].id` | the same thing, short-cut spelling | | `get cat $.kittens[*].name` | two-token form: variable, then a full JsonPath | | `get $.name` / `$.name` | JsonPath on `response`, because a bare `$` roots there | | `get[0] cat.kittens[*].id` | as above, then element 0 of the result | The only functional difference is the index: `get[N]` collapses a list-shaped result to one element, and the `$` short-cut has no index variant. One more thing to get right: **`get` is not a step keyword.** It never begins a step. It prefixes an *expression*, so it appears after `def ... =` or on the **right**-hand side of a `match`. `* get cat.name` is not a valid step, and neither is `get` at the head of a `match` left side: on Karate 1.x `match get[0] cat.kittens[*].id == 23` is split into the variable `get` plus a path and fails with `ReferenceError: get is not defined`. ## The path is literal step text Both spellings share a constraint that catches people out. Karate parses the step and hands the path substring straight to the JsonPath engine. There is no interpolation pass over it. Karate's documentation states the rule plainly for a named path on the left of `match`: it *cannot* be "dynamic" — with in-line variables — so use an extra step if you need that. This is fine most of the time, because you usually know the shape of the payload when you write the test. It bites the moment the **value inside a filter** comes from somewhere else — a previously created id, a name read from a data table, a row of an `Examples:` table. ## `karate.jsonPath` is the escape hatch `karate.jsonPath(json, expression)` takes two arguments: the object to search and the path **as a string**. Because the path is a value rather than step text, you can build it: ```gherkin * def bob = get[0] cat.kittens[?(@.id == 23)] # the filter value now comes from a variable * def temp = karate.jsonPath(cat, "$.kittens[?(@.name=='" + bob.name + "')]") * match temp[0] == bob # or index the result inline * def temp = karate.jsonPath(cat, "$.kittens[?(@.name=='" + bob.name + "')]")[0] * match temp == bob ``` It is equally at home inside a JavaScript function, which is where it usually ends up: ```javascript function (product, id) { return karate.jsonPath(product, "$.partIDs[?(@.id==" + id + ")]").length; } ``` Note that it returns whatever the path returns — an indefinite path still gives you a list — so an index or a `.length` is normally the next thing you write. ## Choosing between them 1. **Static path, value from the step** — use the plain form: `match cat.kittens[*].id == [23, 42]`. Shortest, and the assertion is visible on the line. 2. **Static path, but you need one element** — use `get[0] ...`. 3. **Path depends on a runtime value** — use `karate.jsonPath`, or prepare the value in an earlier step and keep the path static. The second option is often nicer: select once into a variable, then assert against that variable with ordinary steps. 4. **The left side is not a path at all** — wrap it in parentheses and write JavaScript. ## Why the distinction is asked It tests whether a candidate understands that a Karate feature file is *parsed*, not compiled — the text of the step is the program. Someone who has only followed tutorials will assume string interpolation works everywhere and will write a filter with an embedded variable that silently selects nothing. Someone who knows the model will either reach for `karate.jsonPath` or restructure to keep the path static, and will be able to say which they prefer and why. There is a second reason the distinction is worth holding on to. Because the step-text forms are literal, they are **greppable**: you can search a suite for every assertion that touches `kittens` and find them all. A path assembled from string fragments in JavaScript is invisible to that search, and it moves the quoting — single quotes inside double quotes inside a step — into a place where a mistake produces an empty selection rather than a parse error. That is a real cost, and it is why the escape hatch should stay an escape hatch rather than becoming the house style.

  • Does `get` ever start a step?
    No. `get` is an expression prefix, not a step keyword — it appears after `def ... =` or on the right of a `match`, never at the head of a line or of a `match` left side. That is also why `get` does not collide with the HTTP verb in `method get`, which is a different keyword's argument.
  • If the path cannot be dynamic, how do people usually handle a filter value that comes from an `Examples:` row?
    Two ways. Either build the path as a string and pass it to `karate.jsonPath`, or keep the path static and do the narrowing in an earlier step — select the array once, then use `karate.filter` or plain JavaScript over it. The second reads better in a feature file and keeps the assertion line free of quoting.
  • What does `karate.jsonPath` return when the path is indefinite?
    A list, exactly as the step-text forms do — a filter or a wildcard is a query whether you write it in a step or pass it as a string. That is why the documented examples end in `[0]` or `.length`: you index or count the result rather than comparing it to a scalar.

saying these in an interview costs you the question

  • Thinks a variable can be interpolated into a path in step text
  • Says get and the dollar short-cut evaluate differently
  • Treats get as a step keyword that begins a line
  • Believes karate.jsonPath returns a single node for a filter
  • Reaches for the JS API when the path is already static
  • Confuses the HTTP verb in method get with the get prefix