12 KiB

WHAT IS IT?
This library compares two arrays or objects and returns a full diff of their differences.
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, has zero dependencies, and is super fast. Import. Enjoy. 👍
DONORS
I am grateful to the generous donors of Superdiff!
FEATURES
Superdiff exports 4 functions:
// Compares two objects and return a diff for each value and their potential subvalues
getObjectDiff(prevObject, nextObject)
// Compares two arrays and returns a diff for each value
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()
import { getObjectDiff } from "@donedeal0/superdiff";
Compares two objects and return a diff for each value and their potential subvalues. Supports deeply nested objects with any kind of values.
Format
input
prevData: Record<string, unknown>;
nextData: Record<string, unknown>;
options?: {
ignoreArrayOrder?: boolean, // false by default,
showOnly?: {
statuses: ("added" | "deleted" | "updated" | "equal")[], // [] by default
granularity?: "basic" | "deep" // "basic" by default
}
}
prevData
: the original objectnextData
: the new objectoptions
-
ignoreArrayOrder
: if set totrue
,["hello", "world"]
and["world", "hello"]
will be treated asequal
, 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.
-
output
type ObjectDiff = {
type: "object";
status: "added" | "deleted" | "equal" | "updated";
diff: Diff[];
};
/** recursive diff in case of subproperties */
type Diff = {
property: string;
previousValue: unknown;
currentValue: unknown;
status: "added" | "deleted" | "equal" | "updated";
diff?: Diff[];
};
Usage
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",
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()
import { getListDiff } from "@donedeal0/superdiff";
Compares two arrays and returns a diff for each entry. Supports duplicate values, primitive values and objects.
Format
input
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 listnextList
: the new listoptions
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 itsid
. This option has no effect on other datatypes.ignoreArrayOrder
: if set totrue
,["hello", "world"]
and["world", "hello"]
will be treated asequal
, because the two arrays have the same value, just not in the same order.considerMoveAsUpdate
: if set totrue
themoved
value will be considered asupdated
.
output
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
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",
},
],
}
streamListDiff()
import { streamListDiff } from "@donedeal0/superdiff";
Streams the diff of two object lists, ideal for large lists and maximum performance.
Format
input
prevList: T[],
nextList: T[],
referenceProperty: ReferenceProperty<T>,
options: {
showOnly?: returns only the values whose status you are interested in. (e.g. `["added", "equal"]`), // [] by default
chunksSize?: number, // // 0 by default
considerMoveAsUpdate? boolean; // false by default
}
prevList
: the original object list.nextList
: the new object list.referenceProperty
: a common property in all the objects of your lists (e.g.id
).options
chunksSize
the number of object diffs returned by each stream chunk. If set to0
, each stream will return a single object diff. If set to10
each stream will return 10 object diffs.showOnly
gives you the option to return only the values whose status you are interested in (e.g.["added", "equal"]
).considerMoveAsUpdate
: if set totrue
themoved
value will be considered asupdated
.
output
type StreamListDiff<T extends Record<string, unknown>> = {
currentValue: T | null;
previousValue: T | null;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
status: "added" | "deleted" | "moved" | "updated" | "equal";
};
Usage
input
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.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("The full diff is available"))
diff.on("error", (err)=> console.log(err))
isEqual()
import { isEqual } from "@donedeal0/superdiff";
Checks whether two values are equal.
Options
You can add a third options
parameter to isEqual
.
{
ignoreArrayOrder?: boolean // false by default,
}
ignoreArrayOrder
: if set totrue
,["hello", "world"]
and["world", "hello"]
will be treated asequal
, because the two arrays have the same value, just not in the same order.
Usage
isEqual(
[
{ name: "joe", age: 99 },
{ name: "nina", age: 23 },
],
[
{ name: "joe", age: 98 },
{ name: "nina", age: 23 },
],
);
output
false;
isObject()
import { isObject } from "@donedeal0/superdiff";
Tests whether a value is an object.
Usage
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
. Premium support is also available. https://github.com/sponsors/DoneDeal0
CONTRIBUTING
Pull requests are welcome!