Skip to content

fix: bug fix for highly correlated objects - #133

Closed
jedluhmann wants to merge 0 commit into
andreyvit:masterfrom
jedluhmann:jl-bug-fix-for-highly-correlated-objects
Closed

jedluhmann wants to merge 0 commit into
andreyvit:masterfrom
jedluhmann:jl-bug-fix-for-highly-correlated-objects

Conversation

@jedluhmann

@jedluhmann jedluhmann commented Sep 3, 2026

Copy link
Copy Markdown

This PR fixes a bug in the fuzzy logic that causes the array diffing logic to pick an incorrect winner for objects that have a high degree of similarity.

Test case that demonstrates this bug:
it 'should correctly pick best match based on similarity during scalarize, i.e. class obj2[2] should not be selected as best match for obj1[0], instead order should remain unchanged with added class property for obj1[0]'
See: https://github.com/jedluhmann/json-diff/blob/b6500e9d5669d9f31ebe51d26670dcbab2129bf3/test/json-diff.spec.js#L187

The objects in this test case consist of an array of 3 objects. The only difference between object A and object B is that the B0 contains a new property, class: "prose". A human looking at the two objects can easily detect that a single property was added to the object at index 0. However, the algorithm was selecting A2 as the best match for B0, which resulted in completely replacing A0 with B0.

Previously, this is how the score was determined:

  • deleted/added properties: -30
  • matching property: +20
  • matching property with equal value: +20
  • matching property with unequal value: 0 (Note: no penalty for unequal values)

The following table shows the fuzzy calculations:

 FUZZY SCORES for new obj B compared to candidate A

      A0   A1    A2    
 ----------------------
 B0   90   0     100   <-- Incorrect Winner
 B1   0    200   -     <-- Correct
 B2   50   0     400   <-- Correct

Incorrect Output:

[
  -  {
  -    type: "collection"
  -    format: "paragraph"
  -    data: [
  -      {
  -        type: "verse"
  -        vn: 2
  -      }
  -    ]
  -  }
  +  {
  +    type: "collection"
  +    format: "paragraph"
  +    class: "prose"
  +    data: [
  +      {
  +        type: "verse"
  +        vn: 2
  +      }
  +    ]
  +  }
  ...
  ...
]

So why was this the case?

Comparing B0 to A0:

 - 1 added property: -30
 - 3 matching properties: +60
 - 3 matching values: +60

 Total: 90

Comparing B0 to A3:

 - 4 matchning properties: +80
 - 1 matching value: +20

 Total: 100

The results are now determined by the following:

  • deleted/added properties: -50
  • matching property: +50
  • matching property with equal value: +50
  • mathching property with unequal value: -Math.max(0, 50 - change.score), for most cases: -40 or -50

The following table shows the new fuzzy calculations:

      A0    A1    A2    
 -----------------------
 B0   250   0     110   
 B1   0     200   -     
 B2   60    0     400   

And the now Correct Output:

[
  {
    +    class: "prose"
  }
  ...
  ...
]

Also, the goal in making this change was to make the algorithm conceptually easier to reason about. Subtracting 30 here and adding 20 here and there and then assigning a value of 100 to a matching scalar value only to later run it through score += Math.min(20, Math.max(-10, change.score / 5)); is difficult to reason about from a conceptual standpoint.

My approach was instead to think of each property of having a potential total of 100 points. This is conceptually much easier to grasp and also seems to work well. The only reduction in value that occurs due to recursion is Math.max(0, 50 - change.score) and even this is much easier to reason about. This also allows the algorithm to be progressively enhanced, if needed, by considering how similar nested objects are to one another. However, the fuzzy matching that determines the object that is the best match seems to be a much more significant contribution than the recursive similarity of nested objects. And, if possible, leaving recursive similarity out of the equation makes the entire algorithm much, much easier to reason about.

Let me know if you have any questions or need additional clarification.

@jedluhmann

jedluhmann commented Sep 3, 2026

Copy link
Copy Markdown
Author

@andreyvit, please also note the new debug feature noted in #134. This is an incredibly useful addition.

@jedluhmann
jedluhmann force-pushed the jl-bug-fix-for-highly-correlated-objects branch 3 times, most recently from e0857c9 to 097dae0 Compare September 4, 2026 05:47
@jedluhmann jedluhmann changed the title bug fix for highly correlated objects fix: bug fix for highly correlated objects Sep 5, 2026
@jedluhmann jedluhmann closed this Sep 12, 2026
@jedluhmann
jedluhmann force-pushed the jl-bug-fix-for-highly-correlated-objects branch from 097dae0 to fc57508 Compare September 12, 2026 02:54
@jedluhmann
jedluhmann deleted the jl-bug-fix-for-highly-correlated-objects branch September 12, 2026 03:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant