<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chaitanya Chandurkar</title>
    <description>The latest articles on DEV Community by Chaitanya Chandurkar (@cchandurkar).</description>
    <link>https://dev.to/cchandurkar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4118601%2Fae93939d-f888-4325-8905-bc6f99cea0a7.jpg</url>
      <title>DEV Community: Chaitanya Chandurkar</title>
      <link>https://dev.to/cchandurkar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cchandurkar"/>
    <language>en</language>
    <item>
      <title>Why JSON Array Diffing Is Harder Than It Looks</title>
      <dc:creator>Chaitanya Chandurkar</dc:creator>
      <pubDate>Thu, 10 Sep 2026 08:00:43 +0000</pubDate>
      <link>https://dev.to/cchandurkar/why-json-array-diffing-is-harder-than-it-looks-50d4</link>
      <guid>https://dev.to/cchandurkar/why-json-array-diffing-is-harder-than-it-looks-50d4</guid>
      <description>&lt;p&gt;Change one field in a JSON object, then reorder the surrounding array. A diff can suddenly report that nearly every record changed.&lt;/p&gt;

&lt;p&gt;Many JSON diff tools compare arrays by position. That is exactly right for arrays whose order carries meaning. But when an array represents a collection of records, positional matching can produce a surprising amount of noise.&lt;/p&gt;

&lt;p&gt;Consider these two payloads:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Original&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Carol"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"manager"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Changed&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Carol"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"manager"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"inactive"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"David"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"designer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A positional comparison pairs the first object on the left with the first object on the right, then the second with the second, and so on. Since Carol moved to the beginning, several unchanged users appear to have been modified.&lt;/p&gt;

&lt;p&gt;But a person looking at this data would probably describe only two meaningful changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bob's status changed from &lt;code&gt;active&lt;/code&gt; to &lt;code&gt;inactive&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;David was added.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The records didn't lose their identity just because their positions changed.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/meFO7H6gZN4" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr0yymsluo5gbibibo20n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr0yymsluo5gbibibo20n.png" alt="JSON Semantic Diff matching a reordered users array by userId with 96 percent confidence and reporting one status change and one added user." width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;The users are paired by userId, so reordering is not reported as a modification.
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://jsonsemanticdiff.dev" rel="noopener noreferrer"&gt;JSON Semantic Diff&lt;/a&gt; to explore a different approach: try to identify corresponding records before comparing their fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem is correspondence
&lt;/h2&gt;

&lt;p&gt;Once two objects have been paired correctly, comparing their fields is straightforward. The difficult question is which object on the left corresponds to which on the right.&lt;/p&gt;

&lt;p&gt;It is tempting to solve this by looking for a field named &lt;code&gt;id&lt;/code&gt;. Real payloads rarely make it that simple.&lt;/p&gt;

&lt;p&gt;An identity might be called:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;userId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;orderNumber&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sku&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;email&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;something specific to the domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And sometimes no single field is enough.&lt;/p&gt;

&lt;p&gt;Field names are also not proof. A field called &lt;code&gt;id&lt;/code&gt; can be missing, duplicated, regenerated between responses, or only unique in the tiny sample currently being compared. Conversely, an unfamiliar field may be the most reliable identity in the data.&lt;/p&gt;

&lt;p&gt;So JSON Semantic Diff treats a promising name as a hint, not a rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying to infer identity from the data
&lt;/h2&gt;

&lt;p&gt;For each array of objects, the matcher examines individual fields and field combinations as possible identities. It evaluates each candidate using measurable signals:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;What it asks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Uniqueness&lt;/td&gt;
&lt;td&gt;Does the candidate distinguish records within each input?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Match coverage&lt;/td&gt;
&lt;td&gt;How many records can be paired across the two inputs?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Population overlap&lt;/td&gt;
&lt;td&gt;Does the overlap represent enough of both arrays?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Completeness&lt;/td&gt;
&lt;td&gt;Is the candidate present on the records being compared?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type consistency&lt;/td&gt;
&lt;td&gt;Are its values represented consistently?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Name hint&lt;/td&gt;
&lt;td&gt;Does the field name weakly suggest identity?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These signals answer different questions. A candidate can be unique but useless if none of its values appear in the other document. It can have matching values but cover only a tiny fraction of a much larger array. A familiar field name can help break a close tie, but it should not rescue an otherwise weak candidate.&lt;/p&gt;

&lt;p&gt;The signals are combined into a candidate score. There is also a small complexity penalty for composite candidates, so a single reliable field is preferred to an unnecessarily complex combination.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmq0ztjq82lcyh0cmo1b5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmq0ztjq82lcyh0cmo1b5.png" alt=" Expanded score calculation showing weighted contributions from uniqueness, match coverage, population overlap, completeness, type consistency, and name hint, resulting in a 94 percent candidate score." width="467" height="412"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;The selected identity scored 94%, beating the second candidate by a 14.3-point margin.
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;A high score alone is not enough. Automatic matching must also pass safety gates for uniqueness, cross-input coverage, and the margin over the second-best candidate.&lt;/p&gt;

&lt;p&gt;That last check matters. If two identities receive nearly identical scores, choosing either one may be arbitrary. A matcher that confidently makes the wrong pairing is worse than a positional diff because its output looks clean while hiding an incorrect assumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some records need a composite identity
&lt;/h2&gt;

&lt;p&gt;Inventory data is a good example. Imagine records shaped like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inventory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BOS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SKU-1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;12.99&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NYC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SKU-1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;12.99&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NYC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SKU-2004"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;8.50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;store&lt;/code&gt; is not unique because each store carries many products. &lt;code&gt;sku&lt;/code&gt; is not unique because the same product exists in many stores. Together, however, &lt;code&gt;sku + store&lt;/code&gt; can identify one inventory record.&lt;/p&gt;

&lt;p&gt;After records are paired by that composite identity, the diff can distinguish between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an existing product whose quantity changed,&lt;/li&gt;
&lt;li&gt;an existing product whose price changed,&lt;/li&gt;
&lt;li&gt;a newly added store-product combination, and&lt;/li&gt;
&lt;li&gt;records that only moved to another array position.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/eQtMHFXXHJE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr7szgekjk6og4kid168w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr7szgekjk6og4kid168w.png" alt="Inventory JSON comparison matched by the composite identity sku plus store, with modified quantity and price values and one added record." width="799" height="452"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;Neither sku nor store is sufficient by itself, but the combination identifies an inventory record.
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;Composite inference expands the search space and increases the chance of finding accidentally unique combinations. That is why candidate complexity is penalized and why coverage and population overlap remain important.&lt;/p&gt;

&lt;h2&gt;
  
  
  When inference is ambiguous
&lt;/h2&gt;

&lt;p&gt;Not every array contains a reliable identity.&lt;/p&gt;

&lt;p&gt;Values may be duplicated. Too few records may overlap. A field may be missing on part of the array. Two candidates may be almost equally plausible. Some arrays are genuinely ordered sequences and should be compared by position.&lt;/p&gt;

&lt;p&gt;When the evidence is not strong enough, JSON Semantic Diff falls back to positional matching.&lt;/p&gt;

&lt;p&gt;You can then manually select one or more identity fields when domain knowledge provides information the payload alone cannot establish. Automatic inference should save work when the evidence is strong, without taking control away when it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deterministic and explainable by design
&lt;/h2&gt;

&lt;p&gt;No AI model or fuzzy semantic matching is involved. Given the same inputs and configuration, the matcher produces the same decision.&lt;/p&gt;

&lt;p&gt;The interface exposes the selected candidate, its score, the component signals, the runner-up, and the margin between them. That makes it possible to ask not only &lt;em&gt;what did the matcher choose?&lt;/em&gt; but &lt;em&gt;why did it choose this?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The comparison also runs locally in the browser. The JSON does not need to be uploaded to a server, which is useful when inspecting internal API responses, configuration, test fixtures, or production-shaped data.&lt;/p&gt;

&lt;h2&gt;
  
  
  A cleaner diff needs a cautious matcher
&lt;/h2&gt;

&lt;p&gt;A semantic diff is not merely a prettier rendering of a positional diff. It makes an additional claim about correspondence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These two records represent the same entity, even though they occupy different positions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That claim can remove a large amount of noise, but only when it is well supported. The goal is not to infer an identity as often as possible. It is to recognize when the evidence is strong enough to trust one, and to make uncertainty obvious when it is not.&lt;/p&gt;

&lt;p&gt;JSON Semantic Diff is still in alpha, and real-world payloads will expose cases that clean examples do not. If you have an array that it matches incorrectly, or one it refuses to match when it should, I would especially like to see the smallest reproducible example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jsonsemanticdiff.dev" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Try JSON Semantic Diff&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The project is open source on &lt;a href="https://github.com/cchandurkar/json-semantic-diff" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>json</category>
    </item>
  </channel>
</rss>
