Browse Source

chore: improve list-diff performance (#27)

* chore: improve list-diff perf

* chore: update readme
pull/30/head
DoneDeal0 7 months ago committed by GitHub
parent
commit
6d09c01547
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 30
      README.md
  2. 24
      src/lib/list-diff/index.ts
  3. 179
      src/lib/list-diff/list-diff.test.ts

30
README.md

@ -16,9 +16,9 @@ This library compares two arrays or objects and returns a full diff of their dif @@ -16,9 +16,9 @@ This library compares two arrays or objects and returns a full diff of their dif
## 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.
Most existing solutions return a confusing diff format that often requires extra parsing. They are also limited to object comparison.
**Superdiff** gives you a complete diff for both array <u>and</u> objects in a very readable format. Last but not least, it's battle-tested, has zero dependencies, and is super fast.
**Superdiff** provides a complete and readable diff for both arrays **and** objects. Plus, it's battle-tested, has zero dependencies, and is super fast.
Import. Enjoy. 👍
@ -66,7 +66,7 @@ isObject(data) @@ -66,7 +66,7 @@ isObject(data)
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.
Compares two objects and returns a diff for each value and its possible subvalues. Supports deeply nested objects of any value type.
#### FORMAT
@ -87,7 +87,7 @@ options?: { @@ -87,7 +87,7 @@ options?: {
- `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 have the same value, just not in the same order.
- `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"]`)
@ -226,8 +226,8 @@ Compares two arrays and returns a diff for each entry. Supports duplicate values @@ -226,8 +226,8 @@ Compares two arrays and returns a diff for each entry. Supports duplicate values
- `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 instead of 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 have the same value, just not in the same order.
- `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**
@ -328,18 +328,18 @@ Streams the diff of two object lists, ideal for large lists and maximum performa @@ -328,18 +328,18 @@ Streams the diff of two object lists, ideal for large lists and maximum performa
- `prevList`: the original object list.
- `nextList`: the new object list.
- `referenceProperty`: a common property in all the objects of your lists (e.g. `id`).
- `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 by chunk, `10` = 10 object diffs by chunk).
- `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`.
**Output**
The objects diff are grouped in arrays - called `chunks` - and are consumed thanks to an event listener. You have access to 3 events:
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 complete.
- `error`: to be notified of an error during the stream.
- `finish`: to be notified when the stream is finished.
- `error`: to be notified if an error occurs during the stream.
```ts
interface StreamListener<T extends Record<string, unknown>> {
@ -442,7 +442,7 @@ diff.on("error", (err) => console.log(err)) @@ -442,7 +442,7 @@ diff.on("error", (err) => console.log(err))
import { isEqual } from "@donedeal0/superdiff";
```
Checks whether two values are equal.
Tests whether two values are equal.
#### FORMAT
@ -455,9 +455,9 @@ options: { @@ -455,9 +455,9 @@ options: {
ignoreArrayOrder: boolean; // false by default
},
```
- `a`: the value to compare to the value `b`.
- `b`: the value that will 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 have the same value, just not in the same order.
- `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

24
src/lib/list-diff/index.ts

@ -83,26 +83,26 @@ export const getListDiff = <T>( @@ -83,26 +83,26 @@ export const getListDiff = <T>(
return formatSingleListDiff(prevList as T[], LIST_STATUS.DELETED, options);
}
const diff: ListDiff["diff"] = [];
const prevIndexMatches: number[] = [];
const prevIndexMatches = new Set<number>();
nextList.forEach((nextValue, i) => {
const prevIndex = prevList.findIndex((prevValue, prevIdx) => {
if (prevIndexMatches.has(prevIdx)) {
return false;
}
if (isReferencedObject(prevValue, options.referenceProperty)) {
if (isObject(nextValue)) {
return (
isEqual(
prevValue[options.referenceProperty as string],
nextValue[options.referenceProperty as string],
) && !prevIndexMatches.includes(prevIdx)
return isEqual(
prevValue[options.referenceProperty as string],
nextValue[options.referenceProperty as string],
);
}
return false;
}
return (
isEqual(prevValue, nextValue) && !prevIndexMatches.includes(prevIdx)
);
return isEqual(prevValue, nextValue);
});
if (prevIndex > -1) {
prevIndexMatches.push(prevIndex);
prevIndexMatches.add(prevIndex);
}
const indexDiff = prevIndex === -1 ? null : i - prevIndex;
if (indexDiff === 0 || options.ignoreArrayOrder) {
@ -141,8 +141,8 @@ export const getListDiff = <T>( @@ -141,8 +141,8 @@ export const getListDiff = <T>(
});
prevList.forEach((prevValue, i) => {
if (!prevIndexMatches.includes(i)) {
return diff.splice(i, 0, {
if (!prevIndexMatches.has(i)) {
return diff.push({
value: prevValue,
prevIndex: i,
newIndex: null,

179
src/lib/list-diff/list-diff.test.ts

@ -102,20 +102,6 @@ describe("getListDiff", () => { @@ -102,20 +102,6 @@ describe("getListDiff", () => {
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,
@ -130,6 +116,20 @@ describe("getListDiff", () => { @@ -130,6 +116,20 @@ describe("getListDiff", () => {
indexDiff: -1,
status: "moved",
},
{
value: "mendes",
prevIndex: 1,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: "verratti",
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
],
});
});
@ -145,20 +145,6 @@ describe("getListDiff", () => { @@ -145,20 +145,6 @@ describe("getListDiff", () => {
indexDiff: 0,
status: "equal",
},
{
value: 234,
prevIndex: 1,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 76,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 200,
prevIndex: null,
@ -173,6 +159,20 @@ describe("getListDiff", () => { @@ -173,6 +159,20 @@ describe("getListDiff", () => {
indexDiff: -1,
status: "moved",
},
{
value: 234,
prevIndex: 1,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 76,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
],
});
});
@ -194,13 +194,6 @@ describe("getListDiff", () => { @@ -194,13 +194,6 @@ describe("getListDiff", () => {
type: "list",
status: "updated",
diff: [
{
value: { name: "joe", age: 87 },
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: { name: "paul", age: 32 },
prevIndex: 2,
@ -222,6 +215,13 @@ describe("getListDiff", () => { @@ -222,6 +215,13 @@ describe("getListDiff", () => {
indexDiff: 1,
status: "moved",
},
{
value: { name: "joe", age: 87 },
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
],
});
});
@ -335,13 +335,7 @@ describe("getListDiff", () => { @@ -335,13 +335,7 @@ describe("getListDiff", () => {
indexDiff: -5,
status: "moved",
},
{
value: true,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: true,
prevIndex: 1,
@ -370,13 +364,6 @@ describe("getListDiff", () => { @@ -370,13 +364,6 @@ describe("getListDiff", () => {
indexDiff: 0,
status: "equal",
},
{
value: 13,
prevIndex: 7,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: false,
prevIndex: null,
@ -391,6 +378,20 @@ describe("getListDiff", () => { @@ -391,6 +378,20 @@ describe("getListDiff", () => {
indexDiff: null,
status: "added",
},
{
value: true,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 13,
prevIndex: 7,
newIndex: null,
indexDiff: null,
status: "deleted",
},
],
});
});
@ -423,20 +424,6 @@ describe("getListDiff", () => { @@ -423,20 +424,6 @@ describe("getListDiff", () => {
type: "list",
status: "updated",
diff: [
{
value: true,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 13,
prevIndex: 7,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: false,
prevIndex: null,
@ -451,6 +438,20 @@ describe("getListDiff", () => { @@ -451,6 +438,20 @@ describe("getListDiff", () => {
indexDiff: null,
status: "added",
},
{
value: true,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 13,
prevIndex: 7,
newIndex: null,
indexDiff: null,
status: "deleted",
},
],
});
});
@ -536,13 +537,6 @@ describe("getListDiff", () => { @@ -536,13 +537,6 @@ describe("getListDiff", () => {
type: "list",
status: "updated",
diff: [
{
value: "hello",
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: { id: 8, age: 77 },
prevIndex: 4,
@ -572,18 +566,25 @@ describe("getListDiff", () => { @@ -572,18 +566,25 @@ describe("getListDiff", () => {
status: "equal",
},
{
value: { id: 55, character: { strength: 66 } },
prevIndex: 5,
value: { id: 99, character: { strength: 69 } },
prevIndex: null,
newIndex: 4,
indexDiff: null,
status: "added",
},
{
value: "hello",
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: { id: 99, character: { strength: 69 } },
prevIndex: null,
newIndex: 4,
value: { id: 55, character: { strength: 66 } },
prevIndex: 5,
newIndex: null,
indexDiff: null,
status: "added",
status: "deleted",
},
],
});
@ -646,13 +647,6 @@ describe("getListDiff", () => { @@ -646,13 +647,6 @@ describe("getListDiff", () => {
type: "list",
status: "updated",
diff: [
{
value: "hello",
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: { id: 8, age: 77 },
prevIndex: 4,
@ -682,18 +676,25 @@ describe("getListDiff", () => { @@ -682,18 +676,25 @@ describe("getListDiff", () => {
status: "equal",
},
{
value: { id: 55, character: { strength: 66 } },
prevIndex: 5,
value: { id: 99, character: { strength: 69 } },
prevIndex: null,
newIndex: 4,
indexDiff: null,
status: "added",
},
{
value: "hello",
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: { id: 99, character: { strength: 69 } },
prevIndex: null,
newIndex: 4,
value: { id: 55, character: { strength: 66 } },
prevIndex: 5,
newIndex: null,
indexDiff: null,
status: "added",
status: "deleted",
},
],
});

Loading…
Cancel
Save