Superdiff
Superdiff provides a rich and readable diff for arrays, objects, texts and coordinates. It supports stream and file inputs for handling large datasets efficiently, is battle-tested, has zero dependencies, and offers a top-tier performance.
Install / Use
/learn @DoneDeal0/SuperdiffREADME
WHAT IS IT?
Superdiff provides a rich and readable diff for arrays, objects, texts and coordinates. It supports stream and file inputs for handling large datasets efficiently, is battle-tested, has zero dependencies, and offers a top-tier performance.
ℹ️ The documentation is also available on our website!
<hr/>FEATURES
Superdiff exports 5 functions:
<hr/>
⚔ COMPETITORS
| Feature | superdiff | deep-object-diff | deep-diff | diff | microdiff | | ------------------------------ | --------- | ---------------- | --------- | --------- | --------- | | Object diff | ✅ | ✅ | ✅ | ❌ | ✅ | | List diff | ✅ | ❌ | ⚠️ | ❌ | ⚠️ | | Text diff | ✅ | ❌ | ✅ | ✅ | ❌ | | Coordinates diff | ✅ | ❌ | ❌ | ❌ | ❌ | | Streaming for huge datasets | ✅ | ❌ | ❌ | ❌ | ❌ | | Move detection | ✅ | ❌ | ❌ | ❌ | ❌ | | Output refinement | ✅ | ❌ | ❌ | ❌ | ❌ | | Zero dependencies | ✅ | ✅ | ❌ | ✅ | ✅ |
<sub>Some libraries perform array diffing using index-by-index comparison. This approach cannot reliably detect insertions, deletions, or moves correctly. In those cases, support is marked as partial.</sub>
📊 BENCHMARK
Environment: Node.js 24.12.0 (LTS) • MacBook Pro M2 (2023, Sequoia 15.1) • 16GB RAM.
Method: Warm up runs, then each script is executed 20 times, and we keep the median time. To minimize garbage collection and cross‑benchmark interference, all scenarios are run individually. All benchmark scripts are included so you can reproduce the results locally.
List diff
| Scenario | superdiff | arr-diff | deep-diff | | ------------------------- | ------------- | ---------- | --------- | | 10k items array | 1.84 ms | 32.95 ms | 4.74 ms | | 100k items array | 17.43 ms | 3363.15 ms | 50.36 ms |
Object diff
| Scenario | superdiff | deep-object-diff | deep-diff | microdiff | | ------------------------------ | --------- | ---------------- | --------- | ---------- | | 10k flat object keys | 2.27 ms | 2.44 ms | 39.37 ms | 2.24 ms| | 100k flat object keys | 29.23 ms | 31.86 ms | 3784.50 ms| 29.51 ms | | 100k nested nodes | 4.25 ms | 9.67 ms | 16.51 ms | 7.26 ms |
Text diff
| Scenario | superdiff | diff | | ----------------------- | ------------ | ---------- | | 10k words | 1.38 ms | 3.86 ms | | 100k words | 21.68 ms | 45.93 ms | | 10k sentences | 2.30 ms | 5.61 ms | | 100k sentences | 21.95 ms | 62.03 ms |
<sub>(Superdiff uses its normal accuracy settings to match diff's behavior)</sub>
<hr/>👉 Despite providing a full structural diff with a richer output, Superdiff consistently outperforms or matches the fastest diff libraries. It also scales linearly, even with deeply nested data.
🤝 DONORS
I am grateful to the generous donors of Superdiff!
<div style="display: flex;"><a href="https://github.com/AlexisAnzieu" target="_blank"><img alt="AlexisAnzieu" src="https://raw.githubusercontent.com/DoneDeal0/superdiff/main/assets/donor-anzieu.png" width="72px" height="72px"/></a> <a href="https://github.com/omonk" target="_blank"><img alt="omonk" src="https://raw.githubusercontent.com/DoneDeal0/superdiff/main/assets/donor-monk.png" width="72px" height="72px"/></a> <a href="https://github.com/sneko" target="_blank"><img alt="sneko" src="https://raw.githubusercontent.com/DoneDeal0/superdiff/main/assets/donor-sneko.png" width="72px" height="72px"/></a>
</div>If you or your company uses this library, 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.
getObjectDiff
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
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 object.nextData: the new object.options-
ignoreArrayOrder: iftrue,["hello", "world"]and["world", "hello"]are consideredequal, 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:basicreturns only the main keys whose status matches your query.deepcan return main keys if some of their nested keys' status match your request. The nested keys are filtered accordingly.
-
Output
type ObjectDiff = {
type: "object";
status: "added" | "deleted" | "equal" | "updated";
diff: Diff[];
};
type Diff = {
key: string;
value: unknown;
previousValue: unknown;
status: "added" | "deleted" | "equal" | "updated";
// recursive diff in case of nested keys
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: [
{
key: "id",
value: 54,
previousValue: 54,
status: "equal",
},
{
key: "user",
value: {
name: "joe",
member: false,
hobbies: ["golf", "chess"],
age: 66,
},
previousValue: {
name: "joe",
member: true,
hobbies: ["golf", "football"],
age: 66,
},
+ status: "updated",
diff: [
{
key: "name",
value: "joe",
previousValue: "joe",
status: "equal",
},
+ {
+ key: "member",
+ value: false,
+ previousValue: true,
+ status: "updated",
+ },
+ {
+ key: "hobbies",
+ value: ["golf", "chess"],
+ previousValue: ["golf", "football"],
+ status: "updated",
+ },
{
key: "age",
value: 66,
previousValue: 66,
status: "equal",
},
],
},
],
}
<hr/>
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
referenceKey?: string, // "" by default
ignoreArrayOrder?: boolean, // false by default,
considerMoveAsUpdate?: boolean // false by default
}
prevList: the original list.nextList: the new list.optionsshowOnlygives you the option to return only the values whose status you are interested in (e.g.["added", "equal"]).referenceKeywill consider an object to beupdatedrather thanaddedordeletedif one of its keys remains stable, such as itsid. This option has no effect on other datatypes.ignoreArrayOrder: iftrue,["hello", "world"]and["world", "hello"]are consideredequal, because the two arrays contain the same values, just in a different order.considerMoveAsUpdate: iftrue, amovedvalue is consideredupdated.
Output
type ListDiff = {
type: "list";
status: "added" | "
