🚀 The Fast, Accurate, JavaScript Objects Diffing & Patching Library
Published: 2024-05-01 14:21:25

Welcome,

I am here to introduce the new JavaScript library for objects diffing & patching.

[@opentf/obj-diff](https://github.com/Open-Tech-Foundation/obj-diff)

[Live Demo with Visualization](https://obj-diff.pages.dev/)

**Features**

* Deep Objects Diffing
    
* Patching
    
* Supports comparing custom object types (via **diffWith**)
    
* TypeScript Support
    
* Cross-Platform
    

**Supported Types**

* Primitives
    
    * Undefined
        
    * Null
        
    * Number
        
    * String
        
    * Boolean
        
    * BigInt
        
* Objects
    
    * Plain Objects, e.g. `{}`
        
    * Array
        
    * Date
        
    * Map
        
    * Set
        

Let's begin with some basics, and we will go through our examples to see the existing solutions and their issues.

## Diffing

The diffing is the method used to compare objects, change detection or objects tracking.

The diff result is called **patches**.

It allows us to send fewer data to the backend to apply the patches.

## Patching

The patching is the method used to re-create the modified object at the other end using the **original** object + **Patches** (the diff result).

## Accuracy

Here, we are going to find out how accurate our library is, and we need similar, popular libraries to compare against them.

So, let's pick three popular libraries.

1. [Microdiff](https://github.com/AsyncBanana/microdiff)
    
2. [just-diff](https://github.com/angus-c/just)
    
3. [deep-object-diff](https://github.com/mattphillips/deep-object-diff)
    

Let us first create a test file.

```javascript
import { diff } from '@opentf/obj-diff';
import mDiff from "microdiff";
import { diff as jDiff } from "just-diff";
import { detailedDiff } from "deep-object-diff";

function run(a, b) {
  try {
    console.log("Micro Diff:");
    console.log(mDiff(a, b));
    console.log();
  } catch (error) {
    console.log("Error: ", error.message);
  }

  try {
    console.log("Just Diff:");
    console.log(jDiff(a, b));
    console.log();
  } catch (error) {
    console.log("Error: ", error.message);
  }

  try {
    console.log("deep-object-diff:");
    console.log(detailedDiff(a, b));
    console.log();
  } catch (error) {
    console.log("Error: ", error.message);
  }

  try {
    console.log("diff:");
    console.log(diff(a, b));
    console.log();
  } catch (error) {
    console.log("Error: ", error.message);
  }
}
```

Let's start our complete testing.

### 1\. Simple with no difference between objects

```javascript
run({}, {});
```

```bash
Micro Diff:
[]

Just Diff:
[]

deep-object-diff:
{
  added: {},
  deleted: {},
  updated: {},
}

diff:
[]
```

### 2\. Simple with single difference

```javascript
run({ a: 1 }, { a: 2 });
```

```bash
Micro Diff:
[
  {
    path: [ "a" ],
    type: "CHANGE",
    value: 2,
    oldValue: 1,
  }
]

Just Diff:
[
  {
    op: "replace",
    path: [ "a" ],
    value: 2,
  }
]

deep-object-diff:
{
  added: {},
  deleted: {},
  updated: {
    a: 2,
  },
}

diff:
[
  {
    t: 2,
    p: [ "a" ],
    v: 2,
  }
]
```

### 3\. Primitives

```javascript
const a = [undefined, null, "string", 0, 1, 1n, true, false];
const b = [undefined, null, "string", 0, 1, 1n, true, false];
run(a, b);
```

```bash
Micro Diff:
[]

Just Diff:
[]

deep-object-diff:
{
  added: {},
  deleted: {},
  updated: {},
}

diff:
[]
```

All Working fine, no issues.

### 4\. Number properties

```javascript
const a = [NaN, Infinity, -Infinity];
const b = [NaN, Infinity, Infinity];
run(a, b);
```

```bash
Micro Diff:
[
  {
    path: [ 2 ],
    type: "CHANGE",
    value: Infinity,
    oldValue: -Infinity,
  }
]

Just Diff:
[
  {
    op: "replace",
    path: [ 0 ],
    value: NaN,
  }, {
    op: "replace",
    path: [ 2 ],
    value: Infinity,
  }
]

deep-object-diff:
{
  added: {},
  deleted: {},
  updated: {
    "0": NaN,
    "2": Infinity,
  },
}

diff:
[
  {
    t: 2,
    p: [ 2 ],
    v: Infinity,
  }
]
```

As you can see the `just-diff` & `deep-object-diff` incorrectly reporting the `NaN` value changed.

### 5\. Simple deep objects

```javascript
const a = {
  a: {
    b: {
      c: [1, 2, 3]
    },
    d: null
  },
  text: 'Hello'
}

const b = {
  a: {
    b: {
      c: [1, 2, 3, 4, 5]
    },
  },
  text: 'Hello World'
}

run(a, b);
```

```bash
Micro Diff:
[
  {
    type: "CREATE",
    path: [ "a", "b", "c", 3 ],
    value: 4,
  }, {
    type: "CREATE",
    path: [ "a", "b", "c", 4 ],
    value: 5,
  }, {
    type: "REMOVE",
    path: [ "a", "d" ],
    oldValue: null,
  }, {
    path: [ "text" ],
    type: "CHANGE",
    value: "Hello World",
    oldValue: "Hello",
  }
]

Just Diff:
[
  {
    op: "remove",
    path: [ "a", "d" ],
  }, {
    op: "replace",
    path: [ "text" ],
    value: "Hello World",
  }, {
    op: "add",
    path: [ "a", "b", "c", 3 ],
    value: 4,
  }, {
    op: "add",
    path: [ "a", "b", "c", 4 ],
    value: 5,
  }
]

deep-object-diff:
{
  added: {
    a: {
      b: {
        c: { '3': 4, '4': 5 }
      },
    },
  },
  deleted: {
    a: {
      d: undefined,
    },
  },
  updated: {
    text: "Hello World",
  },
}

diff:
[
  {
    t: 1,
    p: [ "a", "b", "c", 3 ],
    v: 4,
  }, {
    t: 1,
    p: [ "a", "b", "c", 4 ],
    v: 5,
  }, {
    t: 0,
    p: [ "a", "d" ],
  }, {
    t: 2,
    p: [ "text" ],
    v: "Hello World",
  }
]
```

All Working fine, no issues.

### 6\. Different object types

```javascript
const a = {
  a: 1,
  b: 2,
  c: 3
}
const b = [1, 2, 3]

run(a, b);
```

```bash
Micro Diff:
[
  {
    type: "REMOVE",
    path: [ "a" ],
    oldValue: 1,
  }, {
    type: "REMOVE",
    path: [ "b" ],
    oldValue: 2,
  }, {
    type: "REMOVE",
    path: [ "c" ],
    oldValue: 3,
  }, {
    type: "CREATE",
    path: [ 0 ],
    value: 1,
  }, {
    type: "CREATE",
    path: [ 1 ],
    value: 2,
  }, {
    type: "CREATE",
    path: [ 2 ],
    value: 3,
  }
]

Just Diff:
[
  {
    op: "remove",
    path: [ "c" ],
  }, {
    op: "remove",
    path: [ "b" ],
  }, {
    op: "remove",
    path: [ "a" ],
  }, {
    op: "add",
    path: [ 0 ],
    value: 1,
  }, {
    op: "add",
    path: [ 1 ],
    value: 2,
  }, {
    op: "add",
    path: [ 2 ],
    value: 3,
  }
]

deep-object-diff:
{
  added: {
    "0": 1,
    "1": 2,
    "2": 3,
  },
  deleted: {
    a: undefined,
    b: undefined,
    c: undefined,
  },
  updated: {},
}

diff:
[
  {
    t: 2,
    p: [],
    v: [ 1, 2, 3 ],
  }
]
```

As you can see, the other libraries are reporting changes within the original object, but the actual object type was changed from `plain object` to `Array`.

**Note**: The empty path array `{ p: [] }` in our diff result denotes the `Root` path.

### 7\. Passing null as object

```javascript
const a = {
  a: 1,
  b: 2,
  c: 3
}
const b = null

run(a, b);
```

```bash
Micro Diff:
Error:  newObj is not an Object. (evaluating 'key in newObj')
Just Diff:
Error:  both arguments must be objects or arrays
deep-object-diff:
{
  added: {},
  deleted: {},
  updated: null,
}

diff:
[
  {
    t: 2,
    p: [],
    v: null,
  }
]
```

### 8\. Commonly used object types: Maps & Sets

```javascript
const a = {
  obj: {
    m: new Map([['x', 0], ['y', 1]]),
    s: new Set([1, 2, 3])
  }
}

const b = {
  obj: {
    m: new Map([['x', 1], ['y', 0]]),
    s: new Set([1, 2, 3, 4, 5])
  }
}

run(a, b);
```

```bash
Micro Diff:
[]

Just Diff:
[]

deep-object-diff:
{
  added: {},
  deleted: {},
  updated: {},
}

diff:
[
  {
    t: 2,
    p: [ "obj", "m" ],
    v: Map(2) {
      "x": 1,
      "y": 0,
    },
  }, {
    t: 2,
    p: [ "obj", "s" ],
    v: Set(5) {
      1,
      2,
      3,
      4,
      5,
    },
  }
]
```

## Performance

For performance evaluation, we have created a benchmark file with some objects in our repo.

The following table is the output.

```diff
┌───┬──────────────────┬─────────┬───────────────────┬────────┬─────────┐
│   │ Task Name        │ ops/sec │ Average Time (ns) │ Margin │ Samples │
├───┼──────────────────┼─────────┼───────────────────┼────────┼─────────┤
+ 0 │ diff             │ 252,694 │ 3957.346814404028 │ ±1.60% │ 25270   │
│ 1 │ microdiff        │ 218,441 │ 4577.892286564301 │ ±0.92% │ 21845   │
│ 2 │ deep-object-diff │ 121,385 │ 8238.188318642591 │ ±1.66% │ 12139   │
│ 3 │ just-diff        │ 105,292 │ 9497.35384615396  │ ±1.66% │ 10530   │
│ 4 │ deep-diff        │ 160,802 │ 6218.820533549017 │ ±1.59% │ 16081   │
└───┴──────────────────┴─────────┴───────────────────┴────────┴─────────┘
```

## Conclusion

I hope I have justified the **Fast** & **Accurate** aspect of the library.

We have [FAQs](https://github.com/Open-Tech-Foundation/obj-diff#faqs) section; you can learn more there.

Please try to use it in your next project and feel free to send your feedback & issues you encounter via GitHub issues.

Please don't forget to check out our `important` Articles:

* [**Introducing Our New JavaScript Standard Library**](https://dev.to/thangaganapathy/introducing-our-new-javascript-standard-library-4k22)
    
* [**You Don’t Need JavaScript Native Methods!**](https://dev.to/thangaganapathy/you-dont-need-javascript-native-methods-58bo)
    

Happy coding! 🚀

🙏 Thanks for reading.