Welcome, I am here to introduce the new JavaScript library for object diffing & patching. @opentf/obj-diff Live Demo with Visualization 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 object 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. Microdiff just-diff deep-object-diff 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. 1. Simple with no difference between objects run({}, {}); Micro Diff: [] Just Diff: [] deep-object-diff: { added: {}, deleted: {}, updated: {}, } diff: [] 2. Simple with a single difference 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, } ] 3. Primitives 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. 4. Number properties 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. 5. Simple, deep objects 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. 6. Different object types 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. 7. Passing null as object 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, } ] 8. Commonly used object types: Maps & Sets 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, }, } ] Performance 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 │ └───┴──────────────────┴─────────┴───────────────────┴────────┴─────────┘ Conclusion 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: Introducing Our New JavaScript Standard Library You Don’t Need JavaScript Native Methods! Happy coding! 🚀 🙏 Thanks for reading. Welcome, I am here to introduce the new JavaScript library for object diffing & patching. @opentf/obj-diff @opentf/obj-diff Live Demo with Visualization Live Demo with Visualization Features Features Deep Objects Diffing Patching Supports comparing custom object types (via diffWith) TypeScript Support Cross-Platform Deep Objects Diffing Deep Objects Diffing Patching Patching Supports comparing custom object types (via diffWith) Supports comparing custom object types (via diffWith ) diffWith TypeScript Support TypeScript Support Cross-Platform Cross-Platform Supported Types Supported Types Primitives Undefined Null Number String Boolean BigInt Objects Plain Objects, e.g. {} Array Date Map Set Primitives Undefined Null Number String Boolean BigInt Undefined Null Number String Boolean BigInt Undefined Undefined Null Null Number Number String String Boolean Boolean BigInt BigInt Objects Plain Objects, e.g. {} Array Date Map Set Plain Objects, e.g. {} Array Date Map Set Plain Objects, e.g. {} Plain Objects, e.g. {} {} Array Array Date Date Map Map Set 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 object tracking. The diff result is called patches . 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). original Patches 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. Microdiff just-diff deep-object-diff Microdiff Microdiff Microdiff just-diff just-diff just-diff deep-object-diff deep-object-diff deep-object-diff 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); } } 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 run({}, {}); run({}, {}); Micro Diff: [] Just Diff: [] deep-object-diff: { added: {}, deleted: {}, updated: {}, } diff: [] Micro Diff: [] Just Diff: [] deep-object-diff: { added: {}, deleted: {}, updated: {}, } diff: [] 2. Simple with a single difference run({ a: 1 }, { a: 2 }); 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, } ] 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 const a = [undefined, null, "string", 0, 1, 1n, true, false]; const b = [undefined, null, "string", 0, 1, 1n, true, false]; run(a, b); 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: [] Micro Diff: [] Just Diff: [] deep-object-diff: { added: {}, deleted: {}, updated: {}, } diff: [] All Working fine - no issues. 4. Number properties const a = [NaN, Infinity, -Infinity]; const b = [NaN, Infinity, Infinity]; run(a, b); 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, } ] 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. just-diff deep-object-diff NaN 5. Simple, deep objects 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); 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", } ] 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 const a = { a: 1, b: 2, c: 3 } const b = [1, 2, 3] run(a, b); 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 ], } ] 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 . plain object Array Note : The empty path array { p: [] } in our diff result denotes the Root path. Note { p: [] } Root 7. Passing null as object const a = { a: 1, b: 2, c: 3 } const b = null run(a, b); 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, } ] 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 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); 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, }, } ] 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. ┌───┬──────────────────┬─────────┬───────────────────┬────────┬─────────┐ │ │ 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 │ └───┴──────────────────┴─────────┴───────────────────┴────────┴─────────┘ ┌───┬──────────────────┬─────────┬───────────────────┬────────┬─────────┐ │ │ 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. Fast Accurate We have FAQs section; you can learn more there. FAQs 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: important Introducing Our New JavaScript Standard Library You Don’t Need JavaScript Native Methods! Introducing Our New JavaScript Standard Library Introducing Our New JavaScript Standard Library Introducing Our New JavaScript Standard Library Introducing Our New JavaScript Standard Library You Don’t Need JavaScript Native Methods! You Don’t Need JavaScript Native Methods! You Don’t Need JavaScript Native Methods! You Don’t Need JavaScript Native Methods! Happy coding! 🚀 🙏 Thanks for reading.