Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/DoneDeal0/superdiff
Superdiff compares two arrays or objects and returns a full diff of their differences in a readable format.
https://github.com/DoneDeal0/superdiff
array-comparison comparison comparison-tool deep-diff diff object-comparison object-diff objectdiff objectdifference
Last synced: about 2 months ago
JSON representation
Superdiff compares two arrays or objects and returns a full diff of their differences in a readable format.
- Host: GitHub
- URL: https://github.com/DoneDeal0/superdiff
- Owner: DoneDeal0
- Created: 2022-12-23T15:02:15.000Z (almost 2 years ago)
- Default Branch: master
- Last Pushed: 2024-10-30T21:40:01.000Z (about 2 months ago)
- Last Synced: 2024-10-30T22:25:30.165Z (about 2 months ago)
- Topics: array-comparison, comparison, comparison-tool, deep-diff, diff, object-comparison, object-diff, objectdiff, objectdifference
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/@donedeal0/superdiff
- Size: 319 KB
- Stars: 692
- Watchers: 3
- Forks: 6
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
[![CI](https://github.com/DoneDeal0/superdiff/actions/workflows/ci.yml/badge.svg)](https://github.com/DoneDeal0/superdiff/actions/workflows/ci.yml)
[![CD](https://github.com/DoneDeal0/superdiff/actions/workflows/cd.yml/badge.svg)](https://github.com/DoneDeal0/superdiff/actions/workflows/cd.yml)
![NPM Downloads](https://img.shields.io/npm/dy/%40donedeal0%2Fsuperdiff?logo=npm)
![GitHub Tag](https://img.shields.io/github/v/tag/DoneDeal0/superdiff?label=latest%20release)
# WHAT IS IT?
This library compares two arrays or objects and returns a full diff of their differences.
âšī¸ The documentation is also available on our [website](https://superdiff.gitbook.io/donedeal0-superdiff)!
## WHY YOU SHOULD USE THIS LIBRARY
Most existing solutions return a confusing diff format that often requires extra parsing. They are also limited to object comparison.
**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.
Import. Enjoy. đ
## DONORS
I am grateful to the generous donors of **Superdiff**!
## FEATURES
**Superdiff** exports 5 functions:
```ts
// Returns a complete diff of two objects
getObjectDiff(prevObject, nextObject)// Returns a complete diff of two arrays
getListDiff(prevList, nextList)// Streams the diff of two object lists, ideal for large lists and maximum performance
streamListDiff(prevList, nextList, referenceProperty)// Checks whether two values are equal
isEqual(dataA, dataB)// Checks whether a value is an object
isObject(data)
```
### getObjectDiff()
```js
import { getObjectDiff } from "@donedeal0/superdiff";
```Compares two objects and returns a diff for each value and its possible subvalues. Supports deeply nested objects of any value type.
#### FORMAT
**Input**
```ts
prevData: Record;
nextData: Record;
options?: {
ignoreArrayOrder?: boolean, // false by default,
showOnly?: {
statuses: ("added" | "deleted" |Â "updated" | "equal")[], // [] by default
granularity?: "basic" | "deep" // "basic" by default
}
}
```- `prevData`: the original object.
- `nextData`: the new object.
- `options`
- `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.
- `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.**Output**
```ts
type ObjectDiff = {
type: "object";
status: "added" | "deleted" | "equal" | "updated";
diff: Diff[];
};type Diff = {
property: string;
previousValue: unknown;
currentValue: unknown;
status: "added" | "deleted" | "equal" | "updated";
// recursive diff in case of subproperties
diff?: Diff[];
};
```
#### USAGE**Input**
```diff
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**
```diff
{
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",
diff: [
{
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",
},
],
},
],
}
```
### getListDiff()
```js
import { getListDiff } from "@donedeal0/superdiff";
```Compares two arrays and returns a diff for each entry. Supports duplicate values, primitive values and objects.
#### FORMAT
**Input**
```ts
prevList: T[];
nextList: T[];
options?: {
showOnly?: ("added" | "deleted" |Â "moved" | "updated" | "equal")[], // [] by default
referenceProperty?: string, // "" by default
ignoreArrayOrder?: boolean, // false by default,
considerMoveAsUpdate?: boolean // false by default
}
```
- `prevList`: the original list.
- `nextList`: the new list.
- `options`
- `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` rather than `added` or `deleted` if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes.
- `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.
- `considerMoveAsUpdate`: if set to `true` a `moved` value will be considered as `updated`.**Output**
```ts
type ListDiff = {
type: "list";
status: "added" | "deleted" | "equal" | "moved" | "updated";
diff: {
value: unknown;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
status: "added" | "deleted" | "equal" | "moved" | "updated";
}[];
};
```
#### USAGE**Input**
```diff
getListDiff(
- ["mbappe", "mendes", "verratti", "ruiz"],
+ ["mbappe", "messi", "ruiz"]
);
```**Output**
```diff
{
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",
},
],
}
```
### streamListDiff()
```js
// If you are in a server environment
import { streamListDiff } from "@donedeal0/superdiff/server";
// If you are in a browser environment
import { streamListDiff } from "@donedeal0/superdiff/client";
```Streams the diff of two object lists, ideal for large lists and maximum performance.
âšī¸ `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).
#### FORMAT
**Input**
#### Server
> 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.
```ts
prevList: Readable | FilePath | Record[],
nextList: Readable | FilePath | Record[],
referenceProperty: keyof Record,
options: {
showOnly?: ("added" | "deleted" |Â "moved" | "updated" | "equal")[], // [] by default
chunksSize?: number, // 0 by default
considerMoveAsUpdate?: boolean; // false by default
useWorker?: boolean; // true by default
showWarnings?: boolean; // true by default
}
```#### Browser
> 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.
```ts
prevList: ReadableStream> | File | Record[],
nextList: ReadableStream> | File | Record[],
referenceProperty: keyof Record,
options: {
showOnly?: ("added" | "deleted" |Â "moved" | "updated" | "equal")[], // [] by default
chunksSize?: number, // 0 by default
considerMoveAsUpdate?: boolean; // false by default
useWorker?: boolean; // true by default
showWarnings?: boolean; // true by default}
```- `prevList`: the original object list.
- `nextList`: the new object list.
- `referenceProperty`: a property common to all objects in your lists (e.g. `id`).
- `options`
- `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).
- `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`).
- `considerMoveAsUpdate`: if set to `true` a `moved` value will be considered as `updated`.
- `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).
- `showWarnings`: if set to `true`, potential warnings will be displayed in the console.> â ī¸ 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.
**Output**
The objects diff are grouped into arrays - called `chunks` - and are consumed thanks to an event listener. You have access to 3 events:
- `data`: to be notified when a new chunk of object diffs is available.
- `finish`: to be notified when the stream is finished.
- `error`: to be notified if an error occurs during the stream.```ts
interface StreamListener {
on(event: "data", listener: (chunk: StreamListDiff[]) => void);
on(event: "finish", listener: () => void);
on(event: "error", listener: (error: Error) => void);
}type StreamListDiff> = {
currentValue: T | null;
previousValue: T | null;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
status: "added" | "deleted" |Â "moved" | "updated" | "equal";
};
```#### USAGE
**Input**
You can send streams, file paths, or arrays as input:
> If you are in a server environment
```ts
// for a simple array
const stream = [{ id: 1, name: "hello" }]
// for a large array
const stream = Readable.from(list, { objectMode: true });
// for a local file
const stream = path.resolve(__dirname, "./list.json");
```> If you are in a browser environment
```ts
// for a simple array
const stream = [{ id: 1, name: "hello" }]
// for a large array
const stream = new ReadableStream({
start(controller) {
list.forEach((value) => controller.enqueue(value));
controller.close();
},
});
// for a local file
const stream = new File([JSON.stringify(file)], "file.json", { type: "application/json" });
// for a file input
const stream = e.target.files[0];```
> Example```diff
const diff = streamListDiff(
[
- { id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" }
],
[
+ { id: 0, name: "Item 0" },
{ id: 2, name: "Item 2" },
+ { id: 3, name: "Item Three" },
],
"id",
{ chunksSize: 2 }
);
```
**Output**```diff
diff.on("data", (chunk) => {
// first chunk received (2 object diffs)
[
+ {
+ previousValue: null,
+ currentValue: { id: 0, name: 'Item 0' },
+ prevIndex: null,
+ newIndex: 0,
+ indexDiff: null,
+ status: 'added'
+ },
- {
- previousValue: { id: 1, name: 'Item 1' },
- currentValue: null,
- prevIndex: 0,
- newIndex: null,
- indexDiff: null,
- status: 'deleted'
- }
]
// second chunk received (2 object diffs)
[
{
previousValue: { id: 2, name: 'Item 2' },
currentValue: { id: 2, name: 'Item 2' },
prevIndex: 1,
newIndex: 1,
indexDiff: 0,
status: 'equal'
},
+ {
+ previousValue: { id: 3, name: 'Item 3' },
+ currentValue: { id: 3, name: 'Item Three' },
+ prevIndex: 2,
+ newIndex: 2,
+ indexDiff: 0,
+ status: 'updated'
+ },
]
});diff.on("finish", () => console.log("Your data has been processed. The full diff is available."))
diff.on("error", (err) => console.log(err))
```
### isEqual()
```js
import { isEqual } from "@donedeal0/superdiff";
```Tests whether two values are equal.
#### FORMAT
**Input**
```ts
a: unknown,
b: unknown,
options: {
ignoreArrayOrder: boolean; // false by default
},
```
- `a`: the value to be compared to the value `b`.
- `b`: the value to be compared to the value `a`.
- `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.#### USAGE
```ts
isEqual(
[
{ name: "joe", age: 99 },
{ name: "nina", age: 23 },
],
[
{ name: "joe", age: 98 },
{ name: "nina", age: 23 },
],
);
```**Output**
```ts
false;
```
### isObject()
```js
import { isObject } from "@donedeal0/superdiff";
```Tests whether a value is an object.
#### FORMAT
**Input**
```ts
value: unknown;
```- `value`: the value whose type will be checked.
#### USAGE
**Input**
```ts
isObject(["hello", "world"]);
```**Output**
```ts
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`. Premium support is also available. https://github.com/sponsors/DoneDeal0
## CONTRIBUTING
Issues and pull requests are welcome!