Skip to content

πŸš€ The Fast, Accurate, JavaScript Objects Diffing & Patching Library.

License

Notifications You must be signed in to change notification settings

Open-Tech-Foundation/obj-diff

Repository files navigation

Β OPEN TECH FOUNDATION

obj-diff

Build Β  JSR Score

Demo image

The Fast, Accurate, JavaScript Objects Diffing & Patching Library.

LIVE DEMO

Features

  • Deep Objects Diffing

  • Patching

  • Supports comparing custom object types

  • TypeScript Support

  • Cross-Platform

Supported Types

  • Primitives

    • Undefined
    • Null
    • Number
    • String
    • Boolean
    • BigInt
  • Objects

    • Plain Objects, eg: {}
    • Array
    • Date
    • Map
    • Set

Installation

Install it using your favourite package manager.

npm install @opentf/obj-diff
yarn add @opentf/obj-diff
pnpm add @opentf/obj-diff
bun add @opentf/obj-diff
deno add @opentf/obj-diff

Usage

import { diff } from '@opentf/obj-diff';

diff(obj1: object, obj2: object): Array<DiffResult>
type DiffResult = {
  t: 0 | 1 | 2; // The type of diff, 0 - Deleted, 1 - Created, 2 - Updated
  p: Array<string | number>; // The object path
  v?: unknown; // The current value
};

Examples

  1. Diff two simple objects.
const a = { a: 1, b: 2 };
const b = { a: 2, c: 3 };

diff(a, b);
/*
[
  {
    t: 2,
    p: ["a"],
    v: 2,
  },
  {
    t: 0,
    p: ["b"],
  },
  {
    t: 1,
    p: ["c"],
    v: 5,
  },
]
*/
  1. Diff two arrays.
const a = [1, 2, 3, 4, 5];
const b = [1, 3, 5];

diff(a, b);
/* 
[
  {
    t: 2,
    p: [1],
    v: 3,
  },
  {
    t: 2,
    p: [2],
    v: 5,
  },
  {
    t: 0,
    p: [3],
  },
  {
    t: 0,
    p: [4],
  },
]
*/
  1. Deep diff two objects.
const a = {
  foo: {
    bar: {
      a: ["a", "b"],
      b: 2,
      c: ["x", "y"],
      e: 100,
    },
  },
  buzz: "world",
};

const b = {
  foo: {
    bar: {
      a: ["a"],
      b: 2,
      c: ["x", "y", "z"],
      d: "Hello, world!",
    },
  },
  buzz: "fizz",
};

diff(a, b);
/*
[
  {
    t: 0,
    p: ["foo", "bar", "a", 1],
  },
  {
    t: 1,
    p: ["foo", "bar", "c", 2],
    v: "z",
  },
  {
    t: 0,
    p: ["foo", "bar", "e"],
  },
  {
    t: 1,
    p: ["foo", "bar", "d"],
    v: "Hello, world!",
  },
  {
    t: 2,
    p: ["buzz"],
    v: "fizz",
  },
]
*/

Patching

You can apply the diff result onto the original object to get the modified object.

import { diff, patch } from "@opentf/obj-diff";

const a = { a: 1, b: 2 };
const b = { a: 2, c: 3 };

const out = patch(a, diff(a, b));

assert.deepStrictEqual(out, b); // ok

Comparing Custom Types

By default, the diff function cannot compare every object types other than the supported list above.

You can extend the default diff function using the diffWith function.

Now you can compare any object types of your own.

Usage - diffWith()

import { diffWith } from '@opentf/obj-diff';

diffWith(
  obj1: object,
  obj2: object,
  fn: (a: object, b: object) => boolean | undefined
): Array<DiffResult>

Examples

Let us compare the MongoDB bson ObjectId objects.

import { ObjectId } from "bson";
import { diffWith } from "@opentf/obj-diff";

const record1 = {
  _id: new ObjectId(),
  title: "Article 1",
  desc: "The article description.",
};

const record2 = {
  _id: new ObjectId(),
  title: "Article 1",
  desc: "The new article description.",
};

const result = diffWith(record1, record2, (a, b) => {
  if (a instanceof ObjectId && b instanceof ObjectId) {
    return a.toString() !== b.toString();
  }
});

console.log(result);
/*
[
  {
    t: 2,
    p: [ "_id" ],
    v: new ObjectId('663088b877dd3c9aaec482d4'),
  }, 
  {
    t: 2,
    p: [ "desc" ],
    v: "The new article description.",
  }
]
*/

FAQs

1. Why the standard JSON Patch protocol is not supported?

The JSON Patch protocol is complicated in nature. And simply we don't want use it as our existing solution works for most of the projects.

2. What is the meaning of empty array {p: []} in path property?

The empty path denotes Root path, and it simply means the entire object was replaced.

For Eg:

diff({}, null); //=> [{t: 2, p: [], v: null}]

Benchmark

β”Œβ”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   β”‚ 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   β”‚
β””β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Running benchmarks

$ bun run build
$ bun benchmark.js

Articles

Please read our important articles:

License

Copyright (c) Thanga Ganapathy (MIT License).