skip to content

In a Karate feature file, `* match order == { id: 1 }` fails with `data types don't match`. What causes that, and what does `* match order != { id: 1 }` do in the same situation?

level: middleimportance: should knowfreq 40%

answer

  1. one check runs before the contents
  2. a shape statement, not a value statement
  3. text that was never parsed
  4. the parentheses name both kinds
  5. the negated form passes too easily

basics

~20 s

Karate compares the two sides' data types before their contents, so a string on the left and an object on the right can never be equal and the step reports data types don't match. The != form passes for exactly that reason.

solid answer

~50 s

Before comparing any content, Karate checks that the two operands are the same kind of value. If they are not — a string against a map, a number against a string, a list against an object — the comparison stops with `data types don't match`, and the report's parentheses show the pair, for example `(STRING:MAP)`. The usual cause is a payload that is still text: Karate does not auto-parse a JSON-looking string, so `* def raw = '{ "id": 1 }'` leaves `raw` a string, and comparing it to an object literal fails on type. The `json` keyword converts it — `* json parsed = raw` — after which `==` compares structures. `!=` inverts the whole check, so a type mismatch makes the negated form **pass**: `match order != { id: 1 }` succeeds not because the data differs meaningfully but because a string is not an object.

code

gherkin · 12 lines
gherkin
Feature: data types must match

  Scenario: a JSON-looking string is still a string
    * def raw = '{ "id": 1 }'
    * match raw != { id: 1 }
    * json parsed = raw
    * match parsed == { id: 1 }

  Scenario: a quoted number is not a number
    * def id = '1'
    * match id != 1
    * match id == '1'

go deeper

for a junior

Recall that data types don't match means the two sides were different kinds of value — most often a payload that is still a string rather than a parsed object.

for a middle

Explain that the type tags are compared before any content, name the json keyword as the conversion, and note that numeric leniency applies only between two numbers.

for a senior

Read the type pair first when triaging a red run: a shape change is usually a contract question for the service team, a value drift is a data question for the test.

for a principal

Set the expectation that assertions state what a payload is, not what it is not. A suite full of negated checks degrades into a suite that cannot go red.

## Type first, content second Karate's comparison engine tags each side with a data type — null, boolean, number, string, list, map, XML node, byte array — and compares those tags before it compares anything else. When the tags disagree, the step fails with `data types don't match`, and the failure line reports the pair in parentheses: ``` match failed: EQUALS $ | data types don't match (STRING:NULL) '' null ``` Nothing recurses in that case. There is no attempt to coerce, parse or stringify one side into the other's shape, so the reason is a shape statement, not a value statement. ## The three causes you actually meet 1. **A payload that is still text.** Karate does not auto-parse a string that happens to look like JSON. `* def raw = '{ "id": 1 }'` produces a string, and `match raw == { id: 1 }` fails on `(STRING:MAP)`. Converting it is a keyword away: `* json parsed = raw` parses the string into a map, after which `==` compares structures. This is the single most common form of the failure, and it usually means a response arrived with a content type Karate did not treat as JSON. 2. **A scalar that changed kind.** `match order.id == 1` against an `id` of `'1'` reports `(STRING:NUMBER)`. Note the asymmetry with numbers: Karate compares numbers to numbers by *value*, so an integer, a double and a BigDecimal holding the same amount are all equal. A quoted number is a different type entirely, and no numeric leniency applies to it. 3. **A container that changed shape.** An endpoint that used to return an object and now returns a single-element list produces `(LIST:MAP)`. It is a contract change, and the type pair says so more clearly than any value diff would. ## What `!=` does here, and why it is a trap `!=` is not a separate comparison. Karate runs the full equality check and passes when that check fails. Every reason `==` can fail is therefore a reason `!=` passes — including a type mismatch. That makes the negated form dangerously easy to satisfy. `match order != { id: 1 }` passes when: - the payload holds a genuinely different `id`; - the payload carries an extra key; - the payload is a string rather than an object; - the payload is `null`; - the path on the left resolved to nothing at all. Only the first of those is what the author usually meant. A `!=` assertion that stays green while an endpoint returns nothing is not a hypothetical — it is the default behaviour. Treat `!=` as an assertion of last resort, and prefer asserting what the payload *is* over asserting what it is not. ## The one exception to the type check If the expected side is a string beginning with `#`, Karate does not treat a type disagreement as a failure. That prefix marks a placeholder rather than a literal, and those are evaluated by their own rules against whatever the actual value turns out to be. Everything else — an ordinary string, a number, a boolean, a map, a list — goes through the strict type check first. ## Diagnosing it from the report | type pair | what it usually means | |---|---| | `(STRING:MAP)` | the body was never parsed — convert with the `json` keyword | | `(STRING:NUMBER)` | a numeric field is quoted in the payload | | `(NULL:NUMBER)` | the node really held JSON `null` — a path that resolved to nothing is a different failure, `actual path does not exist (STRING:NUMBER)` | | `(LIST:MAP)` | the container shape changed — a contract question | | `(NUMBER:NUMBER)` | not a type problem at all; the values differ | The last row is the useful control. If the parentheses hold two identical type names, stop looking for a shape problem and read the two values. ## The habit worth forming Read the parentheses before the values. A type mismatch and a value mismatch are different investigations with different owners — one is usually a contract or a parsing question for the service team, the other is a data question for the test. The report separates them for free, on every failing line, and it is the fastest signal in the whole output.

  • In Karate, does the strict type check mean `match total == 1000` fails when the payload holds 1000 as a BigDecimal?
    No. Both sides are numbers, so the type tags agree and the comparison proceeds by numeric value — an integer, a double and a BigDecimal holding the same amount all compare equal. The type check separates kinds of value, not the Java classes used to hold them.
  • Why is `match response != { id: 1 }` a weak assertion in a Karate suite?
    Because it passes for every reason the equality check can fail: a different value, a surplus key, a string body that was never parsed, a null, or a left-hand path that resolved to nothing. An endpoint returning nothing at all keeps it green. Assert what the payload is rather than what it is not.

saying these in an interview costs you the question

  • Expects Karate to auto-parse a JSON-looking string
  • Thinks a quoted number still equals a numeric literal
  • Reads a type mismatch as a value difference
  • Believes != is a meaningfully strong assertion
  • Assumes the failure recurses into the payload anyway
  • Claims numbers of different Java types cannot compare equal