skip to content

In a Karate feature file, a predicate such as `'#? _ == $.celsius * 1.8 + 32'` reaches a sibling field under a plain `match`, but the same `$` path no longer reaches one under `match each`. Why, and which binding replaces it?

level: seniorimportance: should knowfreq 29%

answer

  1. One name is pinned, one moves
  2. Iteration changes the path, not the root
  3. There is a third magic name here
  4. Look for a binding scoped to the iteration

basics

~20 s

The dollar sign is pinned to the root of the match statement's actual side, which under match each is the whole array, not the element. Karate binds _$ to the current element, so sibling fields go through _$.

solid answer

~40 s

Karate binds two magic names before evaluating a predicate: `_` is the actual value at that exact position, and `$` is the **root of the actual side of the enclosing `match` step** — not the response, and not the current element. Under `match temperature contains { ... }` that root is `temperature`, so `$.celsius` reaches a sibling field. Under `match each hotels contains { ... }` the root is still `hotels`, the **whole array**: the iteration descends the path but keeps the original root, so a sibling-field path under `$` no longer resolves. `_$` exists for exactly this. During a `match each` pass it is bound to the **current element**, so the sibling becomes `_$.roomInformation[0].roomPrice`. The same `_$` is available inside the round-bracket embedded form as well.

code

gherkin · 11 lines
gherkin
* def json =
  """
  {
    "hotels": [
      { "roomInformation": [{ "roomPrice": 618.4 }], "totalPrice": 618.4  },
      { "roomInformation": [{ "roomPrice": 679.79 }], "totalPrice": 679.79 }
    ]
  }
  """

* match each json.hotels contains { totalPrice: '#? _ == _$.roomInformation[0].roomPrice' }

go deeper

for a junior

Know that a predicate can see more than the single value it is written against, and that the extra names are supplied by the match engine rather than declared anywhere in the feature file.

for a middle

Explain that iteration changes the path but not the root, name all three bindings, and show the sibling-field rewrite that fixes a per-element cross-field rule.

for a senior

Recognise the silent version of this: a predicate moved into a match each still parses and still runs, but no longer expresses the invariant it did before. Review cross-field rules whenever an assertion is moved between statements.

for a principal

Decide how far payload-embedded invariants should go before a rule belongs in a named validator or a separate check. Rules inside the payload keep failures precise, but an invariant nobody can read at a glance stops being an assertion and becomes folklore.

## Three names, three scopes Karate injects magic names into the JavaScript engine before it evaluates a predicate or an embedded expression inside an expected payload. There are three, and they are scoped differently: | Name | Bound to | When | |---|---|---| | `_` | the actual value at this exact position | any predicate or embedded expression | | `_$` | the current element of the iteration | during a `match each` pass | | `$` | the root of the actual side of this `match` step | any predicate or embedded expression | - `_` **moves with the node** — it is rebound at every position the walk reaches. - `$` **does not move at all** — it is fixed when the `match` step begins and stays there for every nested comparison. - `_$` **exists because `$` does not move** — it is the only one of the three that tracks the iteration. ## Why the root does not follow the iteration `match each` walks the list and matches every element against the same expected value. What it changes for each element is the **path** — which is why a failure can say which index broke — but the operation keeps a reference to the **root** it started from, and every nested comparison inherits that same root. So under `match temperature contains { fahrenheit: '#? _ == $.celsius * 1.8 + 32' }` the root is `temperature`, a map, and `$.celsius` reaches a sibling field exactly as you would hope. Move the identical predicate under `match each hotels contains { ... }` and the root is `hotels` — the **whole array**. A sibling-field path evaluated against an array does not find the field, and the assertion cannot do what it looks like it is doing. This is the failure mode worth recognising: nothing about the marker changed, only the statement it lives in, and the predicate quietly stopped meaning what it meant one line earlier. ## The binding that replaces it `_$` is bound, for the duration of each iteration of a `match each`, to the element currently being matched — the whole element, at every depth of the expected payload, and not the immediate parent of `_`. Karate's docs call it the "parent of self" because a marker usually sits directly on a field of the element, and there the element *is* that field's parent. Nest the expected payload and the two part company: `_` is rebound at every node the walk reaches, while `_$` is put once per element and stays put. On a flat element a sibling field is one hop away: ```gherkin * def json = """ { "hotels": [ { "roomInformation": [{ "roomPrice": 618.4 }], "totalPrice": 618.4 }, { "roomInformation": [{ "roomPrice": 679.79 }], "totalPrice": 679.79 } ] } """ * match each json.hotels contains { totalPrice: '#? _ == _$.roomInformation[0].roomPrice' } ``` Read the marker aloud: *this field* (`_`) must equal *a field of the array element being matched* (`_$`). That is a cross-field invariant expressed inside the payload, applied to every element, in one step. ## The embedded-expression twin The same binding works in the round-bracket form, which computes an expected **value** rather than a verdict: ```gherkin * match each json.hotels == { roomInformation: '#array', totalPrice: '#(_$.roomInformation[0].roomPrice)' } ``` Which one to pick: - **Plain equality** — use the embedded form. The failure report shows the computed expected value next to the actual one. - **A relationship, a range, a format** — use the predicate form. A false result reports `evaluated to 'false'` at the element's own path. - **Both in one payload** — perfectly normal, and often the clearest thing to write. ## What to do when the field is outside the root Two options, and both are cheap: 1. **Widen the left-hand side** so the field you need sits under the root. `match response contains { data: { ... } }` keeps `$` at `response`, where `match response.data == { ... }` would have pinned it at `data`. 2. **Lift the value into a variable** with `def` before the match, then name that variable in the predicate. Scenario variables are in scope inside the expression, so `'#? _ == expectedTotal'` works and reads better than a long path. ## Why this matters beyond the trivia Cross-item and cross-field rules are where payload assertions earn their keep — the invariant that a total equals a sum, that a status is consistent with a timestamp, that every child references its parent. Those are precisely the rules a literal expected payload cannot state, so a suite that never uses these bindings tends to compensate by extracting values into variables and asserting them one at a time. That works, but it costs a step per rule and loses the exact JSON path in the failure report. Knowing which of the three names is pinned and which moves is what lets the rule stay where the data is.

  • Does `_$` work inside an embedded expression as well as inside a predicate?
    Yes. `match each hotels == { totalPrice: '#(_$.roomInformation[0].roomPrice)' }` computes the expected value from the current element instead of judging it. Karate binds `_$` for the whole iteration, so both the value-producing round-bracket form and the verdict-producing predicate form can see it. Prefer the embedded form for plain equality — the failure report shows the computed value.
  • You need a field that sits outside the left-hand side of the match. What are the options?
    Widen the left-hand side so the field falls under the root — `match response contains { data: { ... } }` rather than `match response.data == { ... }` — or `def` the value into a variable before the match and reference that variable by name inside the predicate. Scenario variables are in scope there, so the second option often reads better than a long path.

Think of a mail merge run over a batch of orders. _ is the field you are filling in, _$ is the order that field belongs to, and $ is the whole batch you were handed at the start - the batch does not shrink to one order just because you are working through them one at a time.

saying these in an interview costs you the question

  • Expects the root binding to follow the iteration into each element
  • Says the rule has to be rewritten as a JavaScript loop instead
  • Thinks the root binding is always the response payload
  • Confuses the element binding with the value under test
  • Assumes a cross-item rule needs a step definition or a helper class