array-comparisoncomparisoncomparison-tooldeep-diffdiffjson-diffnodejsobject-comparisonobject-diffobjectdiffobjectdifferencereactstreamingstreaming-datatypescript
167 lines
4.5 KiB
167 lines
4.5 KiB
7 months ago
|
import {
|
||
|
DEFAULT_LIST_DIFF_OPTIONS,
|
||
|
LIST_STATUS,
|
||
|
ListDiff,
|
||
|
ListDiffOptions,
|
||
7 months ago
|
} from "@models/list";
|
||
|
import { isEqual, isObject } from "@lib/utils";
|
||
2 years ago
|
|
||
2 years ago
|
function getLeanDiff(
|
||
|
diff: ListDiff["diff"],
|
||
7 months ago
|
showOnly = [] as ListDiffOptions["showOnly"],
|
||
2 years ago
|
): ListDiff["diff"] {
|
||
|
return diff.filter((value) => showOnly?.includes(value.status));
|
||
|
}
|
||
|
|
||
1 year ago
|
function formatSingleListDiff<T>(
|
||
|
listData: T[],
|
||
7 months ago
|
status: LIST_STATUS,
|
||
|
options: ListDiffOptions = { showOnly: [] },
|
||
2 years ago
|
): ListDiff {
|
||
1 year ago
|
const diff = listData.map((data, i) => ({
|
||
2 years ago
|
value: data,
|
||
|
prevIndex: status === LIST_STATUS.ADDED ? null : i,
|
||
|
newIndex: status === LIST_STATUS.ADDED ? i : null,
|
||
|
indexDiff: null,
|
||
|
status,
|
||
|
}));
|
||
|
if (options.showOnly && options.showOnly.length > 0) {
|
||
|
return {
|
||
|
type: "list",
|
||
|
status,
|
||
|
diff: diff.filter((value) => options.showOnly?.includes(value.status)),
|
||
|
};
|
||
|
}
|
||
2 years ago
|
return {
|
||
|
type: "list",
|
||
2 years ago
|
status,
|
||
2 years ago
|
diff,
|
||
2 years ago
|
};
|
||
|
}
|
||
|
|
||
7 months ago
|
function getListStatus(listDiff: ListDiff["diff"]): LIST_STATUS {
|
||
2 years ago
|
return listDiff.some((value) => value.status !== LIST_STATUS.EQUAL)
|
||
|
? LIST_STATUS.UPDATED
|
||
|
: LIST_STATUS.EQUAL;
|
||
2 years ago
|
}
|
||
|
|
||
1 year ago
|
function isReferencedObject(
|
||
7 months ago
|
value: unknown,
|
||
|
referenceProperty: ListDiffOptions["referenceProperty"],
|
||
|
): value is Record<string, unknown> {
|
||
1 year ago
|
if (isObject(value) && !!referenceProperty) {
|
||
|
return Object.hasOwn(value, referenceProperty);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the diff between two arrays
|
||
|
* @param {Array<T>} prevList - The original array.
|
||
|
* @param {Array<T>} nextList - The new array.
|
||
1 year ago
|
* @param {ListOptions} options - Options to refine your output.
|
||
|
- `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.
|
||
1 year ago
|
* @returns ListDiff
|
||
|
*/
|
||
|
export const getListDiff = <T>(
|
||
|
prevList: T[] | undefined | null,
|
||
|
nextList: T[] | undefined | null,
|
||
7 months ago
|
options: ListDiffOptions = DEFAULT_LIST_DIFF_OPTIONS,
|
||
2 years ago
|
): ListDiff => {
|
||
|
if (!prevList && !nextList) {
|
||
|
return {
|
||
|
type: "list",
|
||
2 years ago
|
status: LIST_STATUS.EQUAL,
|
||
2 years ago
|
diff: [],
|
||
|
};
|
||
|
}
|
||
|
if (!prevList) {
|
||
1 year ago
|
return formatSingleListDiff(nextList as T[], LIST_STATUS.ADDED, options);
|
||
2 years ago
|
}
|
||
|
if (!nextList) {
|
||
1 year ago
|
return formatSingleListDiff(prevList as T[], LIST_STATUS.DELETED, options);
|
||
2 years ago
|
}
|
||
|
const diff: ListDiff["diff"] = [];
|
||
2 years ago
|
const prevIndexMatches: number[] = [];
|
||
2 years ago
|
nextList.forEach((nextValue, i) => {
|
||
1 year ago
|
const prevIndex = prevList.findIndex((prevValue, prevIdx) => {
|
||
|
if (isReferencedObject(prevValue, options.referenceProperty)) {
|
||
|
if (isObject(nextValue)) {
|
||
|
return (
|
||
|
isEqual(
|
||
|
prevValue[options.referenceProperty as string],
|
||
7 months ago
|
nextValue[options.referenceProperty as string],
|
||
1 year ago
|
) && !prevIndexMatches.includes(prevIdx)
|
||
|
);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
return (
|
||
2 years ago
|
isEqual(prevValue, nextValue) && !prevIndexMatches.includes(prevIdx)
|
||
1 year ago
|
);
|
||
|
});
|
||
2 years ago
|
if (prevIndex > -1) {
|
||
|
prevIndexMatches.push(prevIndex);
|
||
|
}
|
||
2 years ago
|
const indexDiff = prevIndex === -1 ? null : i - prevIndex;
|
||
10 months ago
|
if (indexDiff === 0 || options.ignoreArrayOrder) {
|
||
1 year ago
|
let nextStatus = LIST_STATUS.EQUAL;
|
||
|
if (isReferencedObject(nextValue, options.referenceProperty)) {
|
||
|
if (!isEqual(prevList[prevIndex], nextValue)) {
|
||
|
nextStatus = LIST_STATUS.UPDATED;
|
||
|
}
|
||
|
}
|
||
2 years ago
|
return diff.push({
|
||
|
value: nextValue,
|
||
|
prevIndex,
|
||
|
newIndex: i,
|
||
|
indexDiff,
|
||
1 year ago
|
status: nextStatus,
|
||
2 years ago
|
});
|
||
|
}
|
||
|
if (prevIndex === -1) {
|
||
|
return diff.push({
|
||
|
value: nextValue,
|
||
|
prevIndex: null,
|
||
|
newIndex: i,
|
||
|
indexDiff,
|
||
2 years ago
|
status: LIST_STATUS.ADDED,
|
||
2 years ago
|
});
|
||
|
}
|
||
|
return diff.push({
|
||
|
value: nextValue,
|
||
|
prevIndex,
|
||
|
newIndex: i,
|
||
|
indexDiff,
|
||
1 year ago
|
status: options.considerMoveAsUpdate
|
||
|
? LIST_STATUS.UPDATED
|
||
|
: LIST_STATUS.MOVED,
|
||
2 years ago
|
});
|
||
|
});
|
||
|
|
||
|
prevList.forEach((prevValue, i) => {
|
||
2 years ago
|
if (!prevIndexMatches.includes(i)) {
|
||
2 years ago
|
return diff.splice(i, 0, {
|
||
|
value: prevValue,
|
||
|
prevIndex: i,
|
||
|
newIndex: null,
|
||
|
indexDiff: null,
|
||
2 years ago
|
status: LIST_STATUS.DELETED,
|
||
2 years ago
|
});
|
||
|
}
|
||
|
});
|
||
2 years ago
|
if (options.showOnly && options?.showOnly?.length > 0) {
|
||
|
return {
|
||
|
type: "list",
|
||
|
status: getListStatus(diff),
|
||
|
diff: getLeanDiff(diff, options.showOnly),
|
||
|
};
|
||
|
}
|
||
2 years ago
|
return {
|
||
|
type: "list",
|
||
2 years ago
|
status: getListStatus(diff),
|
||
2 years ago
|
diff,
|
||
|
};
|
||
|
};
|