In Karate's `match actual == expected`, does the order of keys in a JSON object matter, and does the order of elements in a JSON array matter?
answer
- the two shapes are walked differently
- one is a mapping, one a sequence
- lookup by name versus lookup by index
- the length check comes first for lists
- karate.sort normalises before comparing
basics
~20 sKey order in a JSON object is irrelevant, because Karate looks each expected key up in the actual map by name. Element order in a JSON array is significant: lists are compared index by index after a length check.
solid answer
~40 sThe asymmetry is a consequence of how the two shapes are walked. For an object, Karate iterates the **expected** side's entries and looks each key up in the actual map, so the two documents can list their keys in any order and still match. For an array, it first compares the two lengths and fails immediately if they differ, then descends into each index in turn and compares position *i* against position *i*. Reordering `[ 'pen', 'ink' ]` to `[ 'ink', 'pen' ]` therefore fails, and the report names `$[0]`. The practical consequence is that an endpoint with no guaranteed sort order cannot be asserted with a literal array under `==`: either sort both sides first with `karate.sort()`, or reach for a containment operator that ignores position.
code
gherkin · 9 linesFeature: order rules
Scenario: keys are unordered, elements are not
* def cart = { total: 30, items: [ 'pen', 'ink' ] }
* match cart == { items: [ 'pen', 'ink' ], total: 30 }
* match cart != { total: 30, items: [ 'ink', 'pen' ] }
Scenario: normalise first when the server does not sort
* match karate.sort(cart.items) == [ 'ink', 'pen' ]go deeper
Remember the asymmetry as a single sentence: objects are unordered, arrays are ordered. Getting it backwards produces tests that either never fail or fail constantly.
Explain the mechanism — expected keys are looked up by name in the actual map, while list elements are compared position against position after a length check.
Recognise an unsorted collection endpoint as a contract problem first. Normalising with karate.sort() inside the test is a workaround, not a fix, and it hides the instability from whoever owns the API.
Decide as a standard whether response ordering is part of your API contract at all. That single call determines whether whole-payload array assertions are stable across a whole estate of suites.
## Two shapes, two walks Karate's `==` recurses over a payload, but the two container shapes are not walked the same way, and the difference is deliberate. **Objects are walked by key.** The comparison iterates the entries of the *expected* side and, for each one, asks whether the actual map holds that key and then compares the two values. Nothing about that walk depends on the order the keys were written or the order the server serialised them. `{ total: 30, items: [] }` and `{ items: [], total: 30 }` are the same document as far as `match` is concerned. **Arrays are walked by index.** The comparison first compares the two lengths and, if they differ, fails immediately with `actual array length is not equal to expected` and the two counts. Only then does it descend, comparing element 0 against element 0, element 1 against element 1, and so on. Position is part of the assertion. ## Why the asymmetry is correct It mirrors the data model rather than the syntax. A JSON object is a mapping; two objects with the same entries are the same value regardless of serialisation order, and no sane contract depends on the byte order of keys. A JSON array is a sequence; `[1, 2]` and `[2, 1]` are genuinely different values, and a test that treated them as equal would stop catching real regressions in pagination, sort parameters and ranked results. | aspect | JSON object | JSON array | |---|---|---| | walk | key lookup on the actual side | positional, index by index | | order significant | no | yes | | size rule under `==` | actual may not hold keys the expected side never named | lengths must be equal, checked first | | failure path shown | `$.items` — the key name | `$[0]` — the index | ## The upstream documentation understates this Karate's own README describes `match` as smart because "white-space does not matter, and the order of keys (or data elements) does not matter". The parenthesis is misleading: element order in an array very much does matter under `==`. The README's own worked example quietly keeps its array in the same order while shuffling the object's keys around it. Trust the behaviour, not the sentence — and expect an interviewer who has been bitten by it to probe exactly here. ## What this means when the server does not sort An endpoint that returns rows in whatever order the database handed them back cannot be asserted with a literal array under `==`; the test will pass locally and go red the first time a query plan changes. The options, in rough order of preference: - **Ask the API to sort.** An unspecified order in a response is usually a contract defect, not a test problem. - **Normalise before asserting.** `karate.sort(list)` sorts plain strings and numbers with no arguments, and takes a key function for objects: `karate.sort(items, x => x.sku)`. Sort both sides and `==` becomes meaningful again. - **Assert the set, not the sequence.** Karate has containment operators for exactly this; they compare membership rather than position. - **Assert per element.** `match each` applies one expected shape to every element of a list, which is order-free by construction because every element is checked against the same expectation. ## Where the ordering rule bites, in practice Three recurring cases, all of them real: 1. **A list endpoint with no `ORDER BY`.** The rows come back in whatever order the storage engine chose. The suite is green for months, then a new index changes the plan and every array assertion in it turns red on the same day, with nothing in the service actually broken. 2. **A parallel producer.** Anything assembled by fanning out and collecting results — a search aggregator, a batch enrichment step — has no natural order unless one is imposed at the end. Asserting the sequence there asserts a scheduling accident. 3. **A set that is genuinely a sequence.** Pagination, ranked search results, an audit trail, a workflow's step history. Here the positional walk is exactly what you want, and reaching for a sort or a containment operator would quietly delete the most valuable part of the assertion. The mechanical distinction is the same in all three: the positional walk asserts sequence. In the first two cases the contract never promised a sequence, so the assertion is pinning an accident; in the third it did, so the assertion is pinning the contract. ## Reading the failure Because the two walks label their children differently, the failure report tells you which one broke without any guesswork. An object mismatch descends through key names — `$.a.b.c` — while an array mismatch descends through indices — `$.items[2].sku`. XML follows the same rule with slash-separated paths. If you see an index in the path, position was part of what failed. ## The one thing that is never order-sensitive Whitespace and formatting. Both sides are parsed into data structures before anything is compared, so indentation, line breaks and the choice to quote keys or not have no effect whatsoever on the result. Only the parsed shape matters — and for that shape, objects are unordered and arrays are not.
- In Karate, what is checked first when `==` compares two JSON arrays, and what does that failure look like?The two lengths. If they differ, the step fails right there with `actual array length is not equal to expected` followed by the two counts, and no element is ever compared. That is why a length mismatch gives you a short report while a value mismatch gives you a per-index trail.
- In Karate, how does the failure path differ between an object mismatch and an array mismatch?An object mismatch descends through key names, producing a path like `$.customer.address.city`; an array mismatch descends through positions, producing `$.items[2]`. XML uses slash-separated paths instead. Seeing an index in the path is the signal that position was part of what failed.
saying these in an interview costs you the question
- Says Karate sorts arrays before comparing them
- Claims key order must match because JSON is ordered
- Thinks a reordered array still passes match ==
- Assumes == ignores array length differences
- Quotes the README line that element order does not matter
- Believes whitespace or indentation can affect the result