Skip to content

Build1 publisher3 min readPublished

One reorder makes a positional JSON diff report nine field changes instead of one

The author of JSON Semantic Diff pairs records before comparing their fields, scoring single fields and combinations as identity candidates and refusing to guess when the second-best candidate scores nearly as well.

The Engineer · Build desk

Photograph accompanying One reorder makes a positional JSON diff report nine field changes instead of one
Photo: dev.to

What happened

  • A dev.to post argues that comparing JSON arrays by position is right for arrays whose order carries meaning and noisy for arrays that hold a collection of records.
  • In the author's worked example, reordering three users and flipping one status makes a positional diff mark every paired row as modified, when the data holds one field change and one added record.
  • In the post's inventory example neither store nor sku is unique on its own, while sku plus store together identify a single store-product record.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision The comparison mode belongs to the array rather than the run, because one document can carry an order-bearing list and a record set side by side and a single global setting is wrong for one of them.
  • exposure The failure the author names costs more than noise: a wrong pairing produces a small tidy changeset that survives review, while positional noise at least announces itself.
  • constraint Inference is bounded by the sample in front of it, so on short payloads a field that happens to be unique in those rows can outscore the identity that is actually stable.

Start with the arithmetic on the author's own example. Positional comparison pairs index 0 with index 0, index 1 with index 1, and so on [4]. So Alice pairs with Carol and three fields differ; Bob pairs with Alice and two differ; Carol pairs with Bob and all four differ; David has nothing to pair with and reads as added [3][4]. That is nine field-level differences across three rows, where the data holds one change and one addition [6]. Nine to one, on an array of three records.

The ratio is not a property of array size. Moving one record to the front misaligns every record behind it, so the reported noise tracks how far rows shifted, not how much actually changed [4].

Pairing first is the obvious fix and the hard part is identity. Looking for a field called `id` is the tempting shortcut, and the post lists why it fails: identity may be `userId`, `orderNumber`, `sku`, `email`, or something domain-specific, and sometimes no single field is enough [8]. So the matcher scores individual fields and field combinations, with a small complexity penalty so one reliable field beats an unnecessary composite [11].

That search space grows fast. The inventory record in the post has four fields, which is fifteen non-empty combinations to score, four of them single fields and six of them pairs [17]. It is also where accidental uniqueness lives. In that three-row sample, `store` repeats and `sku` repeats, but `quantity` is 24, 13, 11 and `price` repeats 12.99 [14][16]. A pure uniqueness signal would rank `quantity` above the composite that is actually correct, `sku` plus `store` [14]. Three rows will certify almost any column as a key.

The gates are the real engineering here; the scoring by comparison is simple. Automatic matching has to clear uniqueness, cross-input coverage, and a margin over the second-best candidate [12]. The author's reasoning for the margin check is the strongest line in the post: if two candidates score nearly the same, picking either is arbitrary, and a matcher that confidently makes the wrong pairing is worse than a positional diff because its output looks clean while hiding an incorrect assumption [13]. Positional noise is visible and auditable, while a mispaired diff reads as a small tidy changeset that someone approves.

What the supplied text does not show is a configuration surface. There is no documented option to name the identity key yourself, and no statement of what happens when the gates refuse to pick one [20]. The enumerated signal list is missing from the text, and the closing passage on arrays with no reliable identity breaks off mid-sentence [19]. The remedy on offer, then, is automatic inference paired with a refusal path rather than a configurable setting.

For the automatic path to transfer to your payloads, the candidate identity has to be stable across both documents. The post is explicit that an `id` can be regenerated between responses [9]. If your producer mints surrogate keys per response, the field with the best uniqueness score is the field most likely to mispair every row. And because one document can hold an order-bearing array and a record collection side by side, the mode belongs to the array, not the run [1].

What to watch

  • Whether the project documents what happens when the gates refuse a candidate, and whether it silently falls back to positional pairing.
  • Whether an explicit identity override arrives, so callers can bypass inference on arrays where they already know the key.
  • Publication of the scoring signals and thresholds, since a margin gate is only auditable if the score behind it is visible.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories