{"id":13567759,"url":"https://github.com/DoneDeal0/superdiff","last_synced_at":"2025-04-04T02:32:53.108Z","repository":{"id":65122430,"uuid":"581563437","full_name":"DoneDeal0/superdiff","owner":"DoneDeal0","description":"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. ","archived":false,"fork":false,"pushed_at":"2025-02-23T19:40:47.000Z","size":482,"stargazers_count":913,"open_issues_count":0,"forks_count":8,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-03T12:54:06.112Z","etag":null,"topics":["array-comparison","comparison","comparison-tool","deep-diff","diff","json-diff","nodejs","object-comparison","object-diff","objectdiff","objectdifference","react","streaming","streaming-data","typescript"],"latest_commit_sha":null,"homepage":"https://superdiff.gitbook.io/donedeal0-superdiff","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DoneDeal0.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2022-12-23T15:02:15.000Z","updated_at":"2025-03-31T01:00:05.000Z","dependencies_parsed_at":"2024-01-16T23:30:57.564Z","dependency_job_id":"c0e08440-bb8b-4702-af70-1b9cd0e7d874","html_url":"https://github.com/DoneDeal0/superdiff","commit_stats":{"total_commits":32,"total_committers":2,"mean_commits":16.0,"dds":0.25,"last_synced_commit":"ccddf2887c5995035feb197d39bab2d11338f29b"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DoneDeal0%2Fsuperdiff","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DoneDeal0%2Fsuperdiff/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DoneDeal0%2Fsuperdiff/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DoneDeal0%2Fsuperdiff/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DoneDeal0","download_url":"https://codeload.github.com/DoneDeal0/superdiff/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247014139,"owners_count":20869364,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["array-comparison","comparison","comparison-tool","deep-diff","diff","json-diff","nodejs","object-comparison","object-diff","objectdiff","objectdifference","react","streaming","streaming-data","typescript"],"created_at":"2024-08-01T13:02:41.889Z","updated_at":"2025-04-04T02:32:52.815Z","avatar_url":"https://github.com/DoneDeal0.png","language":"TypeScript","readme":"\u003cimg width=\"722\" alt=\"superdiff-logo\" src=\"https://user-images.githubusercontent.com/43271780/209532864-24d7449e-1185-4810-9423-be5df1fe877f.png\"\u003e\n\n\n[![CI](https://github.com/DoneDeal0/superdiff/actions/workflows/ci.yml/badge.svg)](https://github.com/DoneDeal0/superdiff/actions/workflows/ci.yml)\n[![CD](https://github.com/DoneDeal0/superdiff/actions/workflows/cd.yml/badge.svg)](https://github.com/DoneDeal0/superdiff/actions/workflows/cd.yml)\n![NPM Downloads](https://img.shields.io/npm/dy/%40donedeal0%2Fsuperdiff?logo=npm)\n![GitHub Tag](https://img.shields.io/github/v/tag/DoneDeal0/superdiff?label=latest%20release)\n\n\u003chr/\u003e\n\n# WHAT IS IT?\n\nThis library compares two arrays or objects and returns a full diff of their differences.\n\nℹ️ The documentation is also available on our [website](https://superdiff.gitbook.io/donedeal0-superdiff)!\n\n\u003chr/\u003e\n\n## WHY YOU SHOULD USE THIS LIBRARY\n\nMost existing solutions return a confusing diff format that often requires extra parsing. They are also limited to object comparison.\n\n**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. \n\nImport. Enjoy. 👍\n\n\u003chr/\u003e\n\n## DONORS\n\nI am grateful to the generous donors of **Superdiff**!\n\n \u003cdiv style=\"display: flex;\u003e\n           \n\u003ca href=\"https://github.com/AlexisAnzieu\" target=\"_blank\"\u003e\u003cimg alt=\"AlexisAnzieu\" src=\"https://github.com/DoneDeal0/superdiff/assets/43271780/8e9fb627-36ec-479d-87d4-3ca2cb2a796c\" width=\"72px\" height=\"72px\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/omonk\" target=\"_blank\"\u003e\u003cimg alt=\"omonk\" src=\"https://github.com/DoneDeal0/superdiff/assets/43271780/6c040ab4-f6eb-49bf-a737-d138264abbd7\" width=\"72px\" height=\"72px\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/sneko\" target=\"_blank\"\u003e\u003cimg alt=\"sneko\" src=\"https://github.com/DoneDeal0/superdiff/assets/43271780/2caaa70b-9586-44d6-8b3a-3755bba7b1ca\" width=\"72px\" height=\"72px\"/\u003e\u003c/a\u003e\n\n \u003c/div\u003e\n\n \u003chr/\u003e\n\n## FEATURES\n\n**Superdiff** exports 5 functions:\n\n```ts\n// Returns a complete diff of two objects\ngetObjectDiff(prevObject, nextObject)\n\n// Returns a complete diff of two arrays\ngetListDiff(prevList, nextList)\n\n// Streams the diff of two object lists, ideal for large lists and maximum performance\nstreamListDiff(prevList, nextList, referenceProperty)\n\n// Checks whether two values are equal \nisEqual(dataA, dataB)\n\n// Checks whether a value is an object\nisObject(data)\n```\n\u003chr/\u003e\n\n### getObjectDiff()\n\n```js\nimport { getObjectDiff } from \"@donedeal0/superdiff\";\n```\n\nCompares two objects and returns a diff for each value and its possible subvalues. Supports deeply nested objects of any value type.\n\n#### FORMAT\n\n**Input**\n\n```ts\nprevData: Record\u003cstring, unknown\u003e;\nnextData: Record\u003cstring, unknown\u003e;\noptions?: {\n  ignoreArrayOrder?: boolean, // false by default,\n  showOnly?: {\n    statuses: (\"added\" | \"deleted\" | \"updated\" | \"equal\")[], // [] by default\n    granularity?: \"basic\" | \"deep\" // \"basic\" by default\n  }\n}\n```\n\n- `prevData`: the original object.\n- `nextData`: the new object.\n- `options`\n  - `ignoreArrayOrder`: if set to `true`, `[\"hello\", \"world\"]` and `[\"world\", \"hello\"]` will be treated as `equal`, because the two arrays contain the same values, just in a different order.\n  - `showOnly`: returns only the values whose status you are interested in. It takes two parameters:\n\n    - `statuses`: status you want to see in the output (e.g. `[\"added\", \"equal\"]`)\n      - `granularity`:\n        - `basic` returns only the main properties whose status matches your query.\n        - `deep` can return main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly.\n\n**Output**\n\n```ts\ntype ObjectDiff = {\n  type: \"object\";\n  status: \"added\" | \"deleted\" | \"equal\" | \"updated\";\n  diff: Diff[];\n};\n\ntype Diff = {\n  property: string;\n  previousValue: unknown;\n  currentValue: unknown;\n  status: \"added\" | \"deleted\" | \"equal\" | \"updated\";\n  // recursive diff in case of subproperties\n  diff?: Diff[];\n};\n```\n#### USAGE\n\n**Input**\n\n```diff\ngetObjectDiff(\n  {\n    id: 54,\n    user: {\n      name: \"joe\",\n-     member: true,\n-     hobbies: [\"golf\", \"football\"],\n      age: 66,\n    },\n  },\n  {\n    id: 54,\n    user: {\n      name: \"joe\",\n+     member: false,\n+     hobbies: [\"golf\", \"chess\"],\n      age: 66,\n    },\n  }\n);\n```\n\n**Output**\n\n```diff\n{\n      type: \"object\",\n+     status: \"updated\",\n      diff: [\n        {\n          property: \"id\",\n          previousValue: 54,\n          currentValue: 54,\n          status: \"equal\",\n        },\n        {\n          property: \"user\",\n          previousValue: {\n            name: \"joe\",\n            member: true,\n            hobbies: [\"golf\", \"football\"],\n            age: 66,\n          },\n          currentValue: {\n            name: \"joe\",\n            member: false,\n            hobbies: [\"golf\", \"chess\"],\n            age: 66,\n          },\n+         status: \"updated\",\n          diff: [\n            {\n              property: \"name\",\n              previousValue: \"joe\",\n              currentValue: \"joe\",\n              status: \"equal\",\n            },\n+           {\n+             property: \"member\",\n+             previousValue: true,\n+             currentValue: false,\n+             status: \"updated\",\n+           },\n+           {\n+             property: \"hobbies\",\n+             previousValue: [\"golf\", \"football\"],\n+             currentValue: [\"golf\", \"chess\"],\n+             status: \"updated\",\n+           },\n            {\n              property: \"age\",\n              previousValue: 66,\n              currentValue: 66,\n              status: \"equal\",\n            },\n          ],\n        },\n      ],\n    }\n```\n\u003chr/\u003e\n\n### getListDiff()\n\n```js\nimport { getListDiff } from \"@donedeal0/superdiff\";\n```\n\nCompares two arrays and returns a diff for each entry. Supports duplicate values, primitive values and objects.\n\n#### FORMAT\n\n**Input**\n\n```ts\n  prevList: T[];\n  nextList: T[];\n  options?: {\n    showOnly?: (\"added\" | \"deleted\" | \"moved\" | \"updated\" | \"equal\")[], // [] by default\n    referenceProperty?: string, // \"\" by default\n    ignoreArrayOrder?: boolean, // false by default,\n    considerMoveAsUpdate?: boolean // false by default\n  }\n```\n- `prevList`: the original list.\n- `nextList`: the new list.\n- `options`\n  - `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `[\"added\", \"equal\"]`).\n  - `referenceProperty` will consider an object to be `updated` rather than `added` or `deleted` if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes.\n  - `ignoreArrayOrder`: if set to `true`, `[\"hello\", \"world\"]` and `[\"world\", \"hello\"]` will be treated as `equal`, because the two arrays contain the same values, just in a different order.\n  - `considerMoveAsUpdate`: if set to `true` a `moved` value will be considered as `updated`.\n\n**Output**\n\n```ts\ntype ListDiff = {\n  type: \"list\";\n  status: \"added\" | \"deleted\" | \"equal\" | \"moved\" | \"updated\";\n  diff: {\n    value: unknown;\n    prevIndex: number | null;\n    newIndex: number | null;\n    indexDiff: number | null;\n    status: \"added\" | \"deleted\" | \"equal\" | \"moved\" | \"updated\";\n  }[];\n};\n```\n#### USAGE\n\n**Input**\n\n```diff\ngetListDiff(\n- [\"mbappe\", \"mendes\", \"verratti\", \"ruiz\"],\n+ [\"mbappe\", \"messi\", \"ruiz\"]\n);\n```\n\n**Output**\n\n```diff\n{\n      type: \"list\",\n+     status: \"updated\",\n      diff: [\n        {\n          value: \"mbappe\",\n          prevIndex: 0,\n          newIndex: 0,\n          indexDiff: 0,\n          status: \"equal\",\n        },\n-       {\n-         value: \"mendes\",\n-         prevIndex: 1,\n-         newIndex: null,\n-         indexDiff: null,\n-         status: \"deleted\",\n-       },\n-       {\n-         value: \"verratti\",\n-         prevIndex: 2,\n-         newIndex: null,\n-         indexDiff: null,\n-         status: \"deleted\",\n-       },\n+       {\n+         value: \"messi\",\n+         prevIndex: null,\n+         newIndex: 1,\n+         indexDiff: null,\n+         status: \"added\",\n+       },\n+       {\n+         value: \"ruiz\",\n+         prevIndex: 3,\n+         newIndex: 2,\n+         indexDiff: -1,\n+         status: \"moved\",\n        },\n      ],\n    }\n```\n\u003chr/\u003e\n\n### streamListDiff() \n\n```js\n// If you are in a server environment\nimport { streamListDiff } from \"@donedeal0/superdiff/server\";\n// If you are in a browser environment\nimport { streamListDiff } from \"@donedeal0/superdiff/client\";\n```\n\nStreams the diff of two object lists, ideal for large lists and maximum performance.\n\nℹ️ `streamListDiff` requires ESM support for browser usage. It will work out of the box if you use a modern bundler (Webpack, Rollup) or JavaScript framework (Next.js, Vue.js).\n\n#### FORMAT\n\n**Input**\n\n#### Server\n\n\u003e In a server environment, `Readable` refers to Node.js streams, and `FilePath` refers to the path of a file (e.g., `./list.json`). Examples are provided in the #usage section below.\n\n```ts\n prevList: Readable | FilePath | Record\u003cstring, unknown\u003e[],\n nextList: Readable | FilePath | Record\u003cstring, unknown\u003e[],\n referenceProperty: keyof Record\u003cstring, unknown\u003e,\n options: {\n  showOnly?: (\"added\" | \"deleted\" | \"moved\" | \"updated\" | \"equal\")[], // [] by default\n  chunksSize?: number, // 0 by default\n  considerMoveAsUpdate?: boolean; // false by default\n  useWorker?: boolean; // true by default\n  showWarnings?: boolean; // true by default\n}\n```\n\n#### Browser\n\n\u003e In a browser environment, `ReadableStream` refers to the browser's streaming API, and `File` refers to an uploaded or local file. Examples are provided in the #usage section below.\n\n```ts\n prevList: ReadableStream\u003cRecord\u003cstring, unknown\u003e\u003e | File | Record\u003cstring, unknown\u003e[],\n nextList: ReadableStream\u003cRecord\u003cstring, unknown\u003e\u003e | File | Record\u003cstring, unknown\u003e[],\n referenceProperty: keyof Record\u003cstring, unknown\u003e,\n options: {\n  showOnly?: (\"added\" | \"deleted\" | \"moved\" | \"updated\" | \"equal\")[], // [] by default\n  chunksSize?: number, // 0 by default\n  considerMoveAsUpdate?: boolean; // false by default\n  useWorker?: boolean; // true by default\n  showWarnings?: boolean; // true by default\n\n}\n```\n\n- `prevList`: the original object list.\n- `nextList`: the new object list.\n- `referenceProperty`: a property common to all objects in your lists (e.g. `id`).\n- `options`\n  - `chunksSize` the number of object diffs returned by each streamed chunk. (e.g. `0` = 1 object diff per chunk, `10` = 10 object diffs per chunk).\n  - `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `[\"added\", \"equal\"]`).\n  - `considerMoveAsUpdate`: if set to `true` a `moved` value will be considered as `updated`.\n  - `useWorker`: if set to `true`, the diff will be run in a worker for maximum performance. Only recommended for large lists (e.g. +100,000 items).\n  - `showWarnings`: if set to `true`, potential warnings will be displayed in the console. \n\n\u003e ⚠️ Warning: using Readable streams may impact workers' performance since they need to be converted to arrays. Consider using arrays or files for optimal performance. Alternatively, you can turn the `useWorker` option off.\n\n**Output**\n\nThe objects diff are grouped into arrays - called `chunks` - and are consumed thanks to an event listener. You have access to 3 events: \n  - `data`: to be notified when a new chunk of object diffs is available.\n  - `finish`: to be notified when the stream is finished.\n  - `error`: to be notified if an error occurs during the stream.\n\n```ts\ninterface StreamListener\u003cT\u003e {\n  on(event: \"data\", listener: (chunk: StreamListDiff\u003cT\u003e[]) =\u003e void);\n  on(event: \"finish\", listener: () =\u003e void);\n  on(event: \"error\", listener: (error: Error) =\u003e void);\n}\n\ntype StreamListDiff\u003cT extends Record\u003cstring, unknown\u003e\u003e = {\n  currentValue: T | null;\n  previousValue: T | null;\n  prevIndex: number | null;\n  newIndex: number | null;\n  indexDiff: number | null;\n  status: \"added\" | \"deleted\" | \"moved\" | \"updated\" | \"equal\";\n};\n```\n\n#### USAGE\n\n**Input**\n\nYou can send streams, file paths, or arrays as input:\n\n\u003e If you are in a server environment\n\n```ts\n    // for a simple array\n    const stream = [{ id: 1, name: \"hello\" }]\n    // for a large array \n    const stream = Readable.from(list, { objectMode: true });\n    // for a local file\n    const stream = path.resolve(__dirname, \"./list.json\");\n   \n```\n\n\u003e If you are in a browser environment\n\n```ts\n    // for a simple array \n    const stream = [{ id: 1, name: \"hello\" }]\n    // for a large array \n    const stream = new ReadableStream({\n      start(controller) {\n        list.forEach((value) =\u003e controller.enqueue(value));\n        controller.close();\n      },\n    }); \n    // for a local file\n    const stream = new File([JSON.stringify(file)], \"file.json\", { type: \"application/json\" }); \n    // for a file input\n    const stream = e.target.files[0]; \n\n```\n\u003e Example\n\n```diff\nconst diff = streamListDiff(\n      [ \n-       { id: 1, name: \"Item 1\" },  \n        { id: 2, name: \"Item 2\" },\n        { id: 3, name: \"Item 3\" } \n      ],\n      [\n+       { id: 0, name: \"Item 0\" }, \n        { id: 2, name: \"Item 2\" },\n+       { id: 3, name: \"Item Three\" },\n      ],\n      \"id\", \n      { chunksSize: 2 }\n    );\n```\n \n**Output**\n\n```diff\ndiff.on(\"data\", (chunk) =\u003e {\n      // first chunk received (2 object diffs)\n      [\n+       {\n+         previousValue: null,\n+         currentValue: { id: 0, name: 'Item 0' },\n+         prevIndex: null,\n+         newIndex: 0,\n+         indexDiff: null,\n+         status: 'added'\n+       },\n-       {\n-         previousValue: { id: 1, name: 'Item 1' },\n-         currentValue: null,\n-         prevIndex: 0,\n-         newIndex: null,\n-         indexDiff: null,\n-         status: 'deleted'\n-       }\n      ]\n    // second chunk received (2 object diffs)\n      [\n        {\n          previousValue: { id: 2, name: 'Item 2' },\n          currentValue: { id: 2, name: 'Item 2' },\n          prevIndex: 1,\n          newIndex: 1,\n          indexDiff: 0,\n          status: 'equal'\n        },\n+       {\n+         previousValue: { id: 3, name: 'Item 3' },\n+         currentValue: { id: 3, name: 'Item Three' },\n+         prevIndex: 2,\n+         newIndex: 2,\n+         indexDiff: 0,\n+         status: 'updated'\n+       },\n     ]\n});\n\ndiff.on(\"finish\", () =\u003e console.log(\"Your data has been processed. The full diff is available.\"))\ndiff.on(\"error\", (err) =\u003e console.log(err))\n```\n\n\u003chr/\u003e\n\n### isEqual()\n\n```js\nimport { isEqual } from \"@donedeal0/superdiff\";\n```\n\nTests whether two values are equal.\n\n#### FORMAT\n\n**Input**\n\n```ts\na: unknown,\nb: unknown,\noptions: { \n    ignoreArrayOrder: boolean; // false by default\n     },\n```\n- `a`: the value to be compared to the value `b`.\n- `b`: the value to be compared to the value `a`.\n- `ignoreArrayOrder`: if set to `true`, `[\"hello\", \"world\"]` and `[\"world\", \"hello\"]` will be treated as `equal`, because the two arrays contain the same values, just in a different order.\n\n#### USAGE\n\n\n```ts\nisEqual(\n  [\n    { name: \"joe\", age: 99 },\n    { name: \"nina\", age: 23 },\n  ],\n  [\n    { name: \"joe\", age: 98 },\n    { name: \"nina\", age: 23 },\n  ],\n);\n```\n\n**Output**\n\n```ts\nfalse;\n```\n\u003chr/\u003e\n\n### isObject()\n\n```js\nimport { isObject } from \"@donedeal0/superdiff\";\n```\n\nTests whether a value is an object.\n\n#### FORMAT\n\n**Input**\n\n```ts\nvalue: unknown;\n```\n\n- `value`: the value whose type will be checked.\n\n#### USAGE\n\n**Input**\n\n```ts\nisObject([\"hello\", \"world\"]);\n```\n\n**Output**\n\n```ts\nfalse;\n```\n\n\u003chr/\u003e\n\n### ℹ️ More examples are available in the source code tests.\n\n\n\u003chr/\u003e\n\n## CREDITS\n\nDoneDeal0\n\n## SUPPORT\n\nIf 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`. Premium support is also available. https://github.com/sponsors/DoneDeal0\n\n\u003cbr/\u003e\n\u003ca href=\"https://github.com/sponsors/DoneDeal0\" target=\"_blank\"\u003e\n\u003cimg alt=\"sponsor\" src=\"https://github.com/DoneDeal0/superdiff/assets/43271780/21deb4f3-fee3-4bf9-a945-ed0b77c6f82f\"/\u003e\n\u003c/a\u003e\n\u003cbr/\u003e\n\n## CONTRIBUTING\n\nIssues and pull requests are welcome!\n","funding_links":["https://github.com/sponsors/DoneDeal0"],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDoneDeal0%2Fsuperdiff","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDoneDeal0%2Fsuperdiff","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDoneDeal0%2Fsuperdiff/lists"}