Welcome,
I am here to introduce the new JavaScript library for object diffing & patching.
Features
Deep Objects Diffing
Patching
Supports comparing custom object types (via diffWith)
TypeScript Support
Cross-Platform
Supported Types
Undefined
Null
Number
String
Boolean
BigInt
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.
The diffing is the method used to compare objects, change detection, or object tracking.
The diff result is called patches.
It allows us to send fewer data to the backend to apply the patches.
The patching is the method used to re-create the modified object at the other end using the original object + Patches (the diff result).
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.
Let us first create a test file.
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.
run({}, {});
Micro Diff:
[]
Just Diff:
[]
deep-object-diff:
{
added: {},
deleted: {},
updated: {},
}
diff:
[]
run({ a: 1 }, { a: 2 });
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,
}
]
const a = [undefined, null, "string", 0, 1, 1n, true, false];
const b = [undefined, null, "string", 0, 1, 1n, true, false];
run(a, b);
Micro Diff:
[]
Just Diff:
[]
deep-object-diff:
{
added: {},
deleted: {},
updated: {},
}
diff:
[]
All Working fine - no issues.
const a = [NaN, Infinity, -Infinity];
const b = [NaN, Infinity, Infinity];
run(a, b);
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.
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);
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.
const a = {
a: 1,
b: 2,
c: 3
}
const b = [1, 2, 3]
run(a, b);
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.
const a = {
a: 1,
b: 2,
c: 3
}
const b = null
run(a, b);
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,
}
]
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);
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,
},
}
]
For performance evaluation, we have created a benchmark file with some objects in our repo.
The following table is the output.
┌───┬──────────────────┬─────────┬───────────────────┬────────┬─────────┐
│ │ 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 │
└───┴──────────────────┴─────────┴───────────────────┴────────┴─────────┘
I hope I have justified the Fast & Accurate aspect of the library.
We have FAQs section; you can learn more there.
Please try to use it in your next project, and feel free to send us your feedback & issues you encounter via GitHub issues.
Please don't forget to check out our important
Articles:
Happy coding! 🚀
🙏 Thanks for reading.