Superdiff provides a complete and readable diff for both arrays and objects. Plus, it supports stream and file inputs for handling large datasets efficiently, is battle-tested, has zero dependencies, and is super fast.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
Máté Szentgróti 14d4d5d8f5 build: update `dist` 10 months ago
.github feat: add FUNDING.yml for sponsoring 1 year ago
dist build: update `dist` 10 months ago
src feat: export types 10 months ago
test feat: add considerMoveAsUpdate option 1 year ago
.gitignore feat: base 2 years ago
README.md chore: add npm download badge 11 months ago
index.ts feat: base 2 years ago
jest.config.js feat: start list test 2 years ago
package-lock.json feat: add considerMoveAsUpdate option 1 year ago
package.json feat: add considerMoveAsUpdate option 1 year ago
tsconfig.json feat: add build script 2 years ago
tsup.config.ts feat: minify build + add tests 2 years ago

README.md

superdiff-logo

SUPERDIFF

This library compares two arrays or objects and returns a full diff of their differences.

Superdiff CI NPM Downloads GitHub Tag

WHY YOU SHOULD USE THIS LIBRARY

All other existing solutions return a strange diff format that often requires additional parsing. They are also limited to object comparison. 👎

Superdiff gives you a complete diff for both array and objects in a very readable format. Last but not least, it's battle-tested and super fast. Import. Enjoy. 👍

DONORS

I am grateful to the generous donors of Superdiff!

AlexisAnzieu omonk sneko

DIFF FORMAT COMPARISON

Let's compare the diff format of Superdiff and Deep-diff, the most popular diff lib on npm:

input:

const objectA = {
          id: 54,
          user: {
            name: "joe",
-           member: true,
-           hobbies: ["golf", "football"],
            age: 66,
         },
  }

const objectB = {
        id: 54,
        user: {
            name: "joe",
+           member: false,
+           hobbies: ["golf", "chess"],
            age: 66,
        },
  }

Deep-Diff output:

[
  {
    kind: "E",
    path: ["user", "member"],
    lhs: true,
    rhs: false,
  },
  {
    kind: "E",
    path: ["user", "hobbies", 1],
    lhs: "football",
    rhs: "chess",
  },
];

SuperDiff output:

{
      type: "object",
+     status: "updated",
      diff: [
        {
          property: "id",
          previousValue: 54,
          currentValue: 54,
          status: "equal",
        },
        {
          property: "user",
          previousValue: {
            name: "joe",
            member: true,
            hobbies: ["golf", "football"],
            age: 66,
          },
          currentValue: {
            name: "joe",
            member: false,
            hobbies: ["golf", "chess"],
            age: 66,
          },
+         status: "updated",
          subPropertiesDiff: [
            {
              property: "name",
              previousValue: "joe",
              currentValue: "joe",
              status: "equal",
            },
+           {
+             property: "member",
+             previousValue: true,
+             currentValue: false,
+             status: "updated",
+           },
+           {
+             property: "hobbies",
+             previousValue: ["golf", "football"],
+             currentValue: ["golf", "chess"],
+             status: "updated",
+           },
            {
              property: "age",
              previousValue: 66,
              currentValue: 66,
              status: "equal",
            },
          ],
        },
      ],
    }

FEATURES

Superdiff exports 4 functions:

getObjectDiff()

import { getObjectDiff } from "@donedeal0/superdiff";

Compares two objects and return a diff for each value and their potential subvalues:

  • property name
  • status: added, deleted, equal, updated
  • previous value, current value
  • supports deeply nested objects with any kind of values

format:

type ObjectDiff = {
  type: "object";
  status: "added" | "deleted" | "equal" | "updated";
  diff: {
    property: string;
    previousValue: any;
    currentValue: any;
    status: "added" | "deleted" | "equal" | "updated";
    // only appears if some subproperties have been added/deleted/updated
    subPropertiesDiff?: {
      property: string;
      previousValue: any;
      currentValue: any;
      status: "added" | "deleted" | "equal" | "updated";
      // subDiff is a recursive diff in case of nested subproperties
      subDiff?: SubProperties[];
    }[];
  }[];
};

Options

You can add a third options parameter to getObjectDiff.

{
  ignoreArrayOrder?: boolean // false by default,
  showOnly?: {
    statuses: ("added" | "deleted" | "updated" | "equal")[], // [] by default
    granularity?: "basic" | "deep" // "basic" by default
  }
}
  • ignoreArrayOrder: if set to true, ["hello", "world"] and ["world", "hello"] will be treated as equal, because the two arrays have the same value, just not in the same order.

  • showOnly: returns only the values whose status you are interested in. It takes two parameters:

    • statuses: status you want to see in the output (e.g. ["added", "equal"])
      • granularity:
        • basic returns only the main properties whose status matches your query.
        • deep can return main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly.

getListDiff()

import { getListDiff } from "@donedeal0/superdiff";

Compares two arrays and returns a diff for each value:

  • index change: prevIndex, newIndex, indexDiff
  • status: added, deleted, equal, moved, updated
  • value
  • supports arrays of primitive values and objects
  • supports arrays with duplicate values

format:

type ListDiff = {
  type: "list";
  status: "added" | "deleted" | "equal" | "moved" | "updated";
  diff: {
    value: any;
    prevIndex: number | null;
    newIndex: number | null;
    indexDiff: number | null;
    status: "added" | "deleted" | "equal" | "moved" | "updated";
  }[];
};

Options

You can add a third options parameter to getListDiff.

{
  showOnly?: ("added" | "deleted" | "moved" | "updated" | "equal")[], // [] by default
  referenceProperty?: string; // "" by default
}
  • showOnly gives you the option to return only the values whose status you are interested in (e.g. ["added", "equal"]).
  • referenceProperty will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its id. This option has no effect on other datatypes.

isEqual()

import { isEqual } from "@donedeal0/superdiff";

Tests whether two values are equal.

Options

You can add a third options parameter to isEqual.

{
  ignoreArrayOrder?: boolean // false by default,
}
  • ignoreArrayOrder: if set to true, ["hello", "world"] and ["world", "hello"] will be treated as equal, because the two arrays have the same value, just not in the same order.

isObject()

import { isObject } from "@donedeal0/superdiff";

Tests whether a value is an object.

EXAMPLES

getListDiff()

input

getListDiff(
- ["mbappe", "mendes", "verratti", "ruiz"],
+ ["mbappe", "messi", "ruiz"]
);

output

{
      type: "list",
+     status: "updated",
      diff: [
        {
          value: "mbappe",
          prevIndex: 0,
          newIndex: 0,
          indexDiff: 0,
          status: "equal",
        },
-       {
-         value: "mendes",
-         prevIndex: 1,
-         newIndex: null,
-         indexDiff: null,
-         status: "deleted",
-       },
-       {
-         value: "verratti",
-         prevIndex: 2,
-         newIndex: null,
-         indexDiff: null,
-         status: "deleted",
-       },
+       {
+         value: "messi",
+         prevIndex: null,
+         newIndex: 1,
+         indexDiff: null,
+         status: "added",
+       },
+       {
+         value: "ruiz",
+         prevIndex: 3,
+         newIndex: 2,
+         indexDiff: -1,
+         status: "moved",
        },
      ],
    }

getObjectDiff()

input

getObjectDiff(
  {
    id: 54,
    user: {
      name: "joe",
-     member: true,
-     hobbies: ["golf", "football"],
      age: 66,
    },
  },
  {
    id: 54,
    user: {
      name: "joe",
+     member: false,
+     hobbies: ["golf", "chess"],
      age: 66,
    },
  }
);

output

{
      type: "object",
+     status: "updated",
      diff: [
        {
          property: "id",
          previousValue: 54,
          currentValue: 54,
          status: "equal",
        },
        {
          property: "user",
          previousValue: {
            name: "joe",
            member: true,
            hobbies: ["golf", "football"],
            age: 66,
          },
          currentValue: {
            name: "joe",
            member: false,
            hobbies: ["golf", "chess"],
            age: 66,
          },
+         status: "updated",
          subPropertiesDiff: [
            {
              property: "name",
              previousValue: "joe",
              currentValue: "joe",
              status: "equal",
            },
+           {
+             property: "member",
+             previousValue: true,
+             currentValue: false,
+             status: "updated",
+           },
+           {
+             property: "hobbies",
+             previousValue: ["golf", "football"],
+             currentValue: ["golf", "chess"],
+             status: "updated",
+           },
            {
              property: "age",
              previousValue: 66,
              currentValue: 66,
              status: "equal",
            },
          ],
        },
      ],
    }

isEqual()

isEqual(
  [
    { name: "joe", age: 99 },
    { name: "nina", age: 23 },
  ],
  [
    { name: "joe", age: 98 },
    { name: "nina", age: 23 },
  ]
);

output

false;

isObject()

input

isObject(["hello", "world"]);

output

false;

More examples are available in the source code tests.


CREDITS

DoneDeal0

SUPPORT

If you or your company uses Superdiff, please show your support by becoming a sponsor! Your name and company logo will be displayed on the README.md. https://github.com/sponsors/DoneDeal0


sponsor

CONTRIBUTING

Pull requests are welcome!