In a Karate feature file, `* def data = { a: 'hello', b: null, c: null }` is followed by `* def json = { foo: '#(data.a)', bar: '#(data.b)', baz: '##(data.c)' }`. What does `json` hold, and why?
answer
- One extra character changes the outcome
- Two of the three sources are null
- One key survives, one does not
- Think about deleting versus setting to null
basics
~20 sjson holds { foo: 'hello', bar: null }. The single-hash form substitutes whatever the expression evaluates to, keeping the key even when that value is null; the double-hash form deletes the key outright when its expression evaluates to null.
solid answer
~40 sKarate walks any JSON or XML literal written in a step and replaces values of the form `#(expr)` or `##(expr)` with the result of evaluating `expr`. `#(data.a)` becomes `hello`. `#(data.b)` becomes `null` and the `bar` key survives holding `null`. The extra hash is the whole difference: `##(expr)` is **remove-if-null**, so `baz` disappears from the map rather than being set to `null` — the same effect as the `remove` keyword, and it applies to an XML element or attribute too. The substituted value is **typed**, not stringified, so `'#(count)'` puts a real number into the payload and `'#(customer)'` puts a whole object. That is how one request body serves several cases: the optional fields drop out when their source is null.
code
gherkin · 6 lines* def data = { a: 'hello', b: null, c: null }
* def json = { foo: '#(data.a)', bar: '#(data.b)', baz: '##(data.c)' }
* match json == { foo: 'hello', bar: null }
# one body, several cases: absent values drop out instead of going out as null
* def payload = { name: '#(name)', nickname: '##(nickname)', age: '##(age)' }go deeper
Remember the two spellings and what each does to a null: one keeps the key, the other deletes it. Being able to predict the resulting payload from a short snippet is the whole ask here.
Explain that the substitution is typed rather than textual, that remove-if-null applies to XML nodes as well as JSON keys, and why an API that distinguishes an absent field from an explicit null makes the difference matter.
Use one payload definition driven by variables instead of a family of near-identical bodies, and know the diagnostic: a literal placeholder arriving at a service means an expression was unresolvable when the literal was defined.
Judge how much of a request body should be assembled by substitution. It removes duplication, but a body whose shape depends on which variables happen to be null is harder to reason about than two explicit bodies, and the tipping point is a team decision.
## What Karate substitutes When a step defines a JSON or XML literal, Karate walks the parsed structure and looks at every string value. A value of the form `#(expr)` or `##(expr)` is treated as an **embedded expression**: `expr` is evaluated on the scenario's JavaScript engine and the result replaces the placeholder. ```gherkin * def data = { a: 'hello', b: null, c: null } * def json = { foo: '#(data.a)', bar: '#(data.b)', baz: '##(data.c)' } * match json == { foo: 'hello', bar: null } ``` Three placeholders, three different outcomes, and the difference between the last two is one extra hash. | Placeholder | Expression result | Effect on the payload | |---|---|---| | `#(data.a)` | `'hello'` | key kept, value replaced | | `#(data.b)` | `null` | key kept, holding `null` | | `##(data.c)` | `null` | **key removed entirely** | ## Remove-if-null `##(expr)` is the optional form: if the expression evaluates to `null`, the node is deleted rather than set to `null` — the same effect as calling the `remove` keyword afterwards, but declared where the field is. It applies to: - a **key in a JSON object** — the key disappears from the map entirely; - an **element of a JSON array** — the element is dropped and the list closes up; - an **XML element or attribute** — the node is removed from the document. This is what lets one payload definition serve several cases. Build the request body once, drive the optional parts from variables, and let the fields that have no value disappear instead of going out as explicit nulls — which many APIs treat as "set this to null" rather than "leave it alone". ```gherkin * def payload = { name: '#(name)', nickname: '##(nickname)', age: '##(age)' } ``` Run that with `nickname` null and the request carries no `nickname` key at all. ## The value is typed, not stringified The substitution puts the **evaluated value** into the payload, keeping its type. `'#(count)'` places a number, `'#(tags)'` places an array, `'#(customer)'` places a whole object. That is what makes the form composable: a nested structure can be assembled from parts held in variables, and the result is real JSON, not a string that happens to look like it. That matters in three everyday ways: - a numeric field stays numeric, so a strict `match` against a number still passes; - an object or array can be spliced in whole, so payloads compose from `def`-ed parts; - nothing needs re-parsing afterwards, because the result was never a string to begin with. The same property is what makes it useful on the expected side of a `match`, where the evaluated value becomes the value to compare against: - `'#(expectedTotal)'` — compare this node to a variable's value. - `'#? _ == expectedTotal'` — judge this node with an expression. They look similar and they are not the same tool. The round-bracket form **computes a value**; the question-mark form **computes a verdict**. Reach for the first when the rule is equality, because the failure report then shows the expected value beside the actual one. Reach for the second when the rule is a relationship, a range, or anything else that is not equality. ## Reusable schema fragments Because a substituted value keeps its type, a fragment of markers can be `def`-ed once and referenced wherever that shape appears. There is one wrinkle: if you write the fragment as a plain JSON literal, Karate substitutes the placeholder inside it immediately. Wrapping the definition in round brackets makes it enclosed JavaScript, which keeps the placeholder intact so the match engine sees it later: ```gherkin * def dogSchema = { id: '#string', color: '#string' } * def schema = ({ id: '#string', name: '#string', dog: '##(dogSchema)' }) * match response == schema ``` Here `##(dogSchema)` carries its optionality into the match: the `dog` node may be absent or `null`, and otherwise must match the fragment. ## When the expression cannot be evaluated An embedded expression that fails to evaluate at the moment the literal is defined does **not** fail the step. Karate leaves the placeholder string in place and moves on, because the match engine gets a second chance at it later — which is what makes the schema-fragment pattern above work at all, since a fragment is usually defined before the variables it references exist. The cost is a real diagnostic trap: 1. A typo or an out-of-scope variable inside `#(...)` produces no error at definition time. 2. The literal string `#(orderId)` stays in the structure. 3. If that structure is a request body, it goes out on the wire as data. 4. The symptom surfaces far away — a rejected request, or, on Karate 1.5.2, a mock quietly serving the placeholder to its caller. Karate 2.x closes the mock half of that trap: the runtime records every embedded expression whose evaluation threw, and the mock server tests the finished response body against that record before serving it — a surviving placeholder becomes an HTTP 500 naming the placeholder and its path rather than data on the wire. A real outbound request body is guarded on neither line. So when a request body arrives at a service with a visible `#(...)` in it, the answer is almost always that the expression referenced something that was not defined yet. Check the spelling and check the order of the steps, not the transport.
- How does `'#(expectedTotal)'` differ from `'#? _ == expectedTotal'` on the expected side of a match?The round-bracket form computes a **value**: the expression is evaluated and the result becomes what this node is compared against. The question-mark form computes a **verdict**: the expression itself decides pass or fail. For plain equality prefer the first, because the failure report then shows the expected value beside the actual one instead of only saying an expression came out false.
- A request body reaches the service with a literal `#(orderId)` inside it. What happened?The expression could not be evaluated when the literal was defined — a typo, or a variable that was not in scope yet — and Karate leaves an unevaluated placeholder in place rather than failing the step, because a match may still resolve it later. Nothing resolves it in a request body, so the placeholder ships as data. Check the name and the order of the steps.
- Why does a reusable schema fragment have to be wrapped in round brackets?A bare JSON literal has its placeholders substituted straight away, which would collapse `'##(dogSchema)'` into the fragment and lose the optionality. Wrapping the definition in round brackets makes it enclosed JavaScript, so the placeholder survives into the variable and the match engine sees the double hash when the fragment is finally used.
saying these in an interview costs you the question
- Says both forms set the key to null when the source is null
- Thinks the substituted value is inserted as a string
- Believes an unevaluatable expression fails the step immediately
- Confuses the round-bracket form with the predicate form
- Assumes removing a key requires a separate remove step