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.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

303 lines
7.7 KiB

'use strict';
// src/model.ts
var STATUS = {
ADDED: "added",
EQUAL: "equal",
MOVED: "moved",
DELETED: "deleted",
UPDATED: "updated"
};
// src/utils.ts
function isEqual(a, b) {
if (typeof a !== typeof b)
return false;
if (Array.isArray(a)) {
if (a.length !== b.length) {
return false;
}
return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i]));
}
if (typeof a === "object") {
return JSON.stringify(a) === JSON.stringify(b);
}
return a === b;
}
function isObject(value) {
return !!value && typeof value === "object" && !Array.isArray(value);
}
// src/object-diff.ts
function getObjectStatus(diff) {
return diff.some((property) => property.status !== STATUS.EQUAL) ? STATUS.UPDATED : STATUS.EQUAL;
}
function formatSingleObjectDiff(data, status) {
if (!data) {
return {
type: "object",
status: STATUS.isEqual,
diff: []
};
}
const diff = [];
Object.entries(data).forEach(([property, value]) => {
if (isObject(value)) {
const subPropertiesDiff = [];
Object.entries(value).forEach(([subProperty, subValue]) => {
subPropertiesDiff.push({
name: subProperty,
previousValue: status === STATUS.ADDED ? void 0 : subValue,
currentValue: status === STATUS.ADDED ? subValue : void 0,
status
});
});
return diff.push({
property,
previousValue: status === STATUS.ADDED ? void 0 : data[property],
currentValue: status === STATUS.ADDED ? value : void 0,
status,
subPropertiesDiff
});
}
return diff.push({
property,
previousValue: status === STATUS.ADDED ? void 0 : data[property],
currentValue: status === STATUS.ADDED ? value : void 0,
status
});
});
return {
type: "object",
status,
diff
};
}
function getPreviousMatch(previousValue, nextSubProperty) {
if (!previousValue) {
return void 0;
}
const previousMatch = Object.entries(previousValue).find(
([subPreviousKey]) => isEqual(subPreviousKey, nextSubProperty)
);
return previousMatch ? previousMatch[1] : void 0;
}
function getValueStatus(previousValue, nextValue) {
if (isEqual(previousValue, nextValue)) {
return STATUS.EQUAL;
}
return STATUS.UPDATED;
}
function getPropertyStatus(subPropertiesDiff) {
return subPropertiesDiff.some((property) => property.status !== STATUS.EQUAL) ? STATUS.UPDATED : STATUS.EQUAL;
}
function getDeletedProperties(previousValue, nextValue) {
if (!previousValue)
return void 0;
const prevKeys = Object.keys(previousValue);
const nextKeys = Object.keys(nextValue);
const deletedKeys = prevKeys.filter((prevKey) => !nextKeys.includes(prevKey));
if (deletedKeys.length > 0) {
return deletedKeys.map((deletedKey) => ({
property: deletedKey,
value: previousValue[deletedKey]
}));
}
return void 0;
}
function getSubPropertiesDiff(previousValue, nextValue) {
const subPropertiesDiff = [];
let subDiff;
const deletedMainSubProperties = getDeletedProperties(
previousValue,
nextValue
);
if (deletedMainSubProperties) {
deletedMainSubProperties.forEach((deletedProperty) => {
subPropertiesDiff.push({
name: deletedProperty.property,
previousValue: deletedProperty.value,
currentValue: void 0,
status: STATUS.DELETED
});
});
}
Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => {
const previousMatch = getPreviousMatch(previousValue, nextSubProperty);
if (!!!previousMatch && !!nextSubProperty) {
return subPropertiesDiff.push({
name: nextSubProperty,
previousValue: void 0,
currentValue: nextSubValue,
status: STATUS.ADDED
});
}
if (isObject(nextSubValue)) {
const data = getSubPropertiesDiff(
previousMatch,
nextSubValue
);
if (data && data.length > 0) {
subDiff = data;
}
}
if (previousMatch) {
subPropertiesDiff.push({
name: nextSubProperty,
previousValue: previousMatch,
currentValue: nextSubValue,
status: getValueStatus(previousMatch, nextSubValue),
...!!subDiff && { subDiff }
});
}
});
return subPropertiesDiff;
}
function getObjectDiff(prevData, nextData) {
if (!prevData && !nextData) {
return {
type: "object",
status: STATUS.EQUAL,
diff: []
};
}
if (!prevData) {
return formatSingleObjectDiff(nextData, STATUS.ADDED);
}
if (!nextData) {
return formatSingleObjectDiff(prevData, STATUS.DELETED);
}
const diff = [];
Object.entries(nextData).forEach(([nextProperty, nextValue]) => {
const previousValue = prevData[nextProperty];
if (!!!previousValue) {
return diff.push({
property: nextProperty,
previousValue: void 0,
currentValue: nextValue,
status: STATUS.ADDED
});
}
if (isObject(nextValue)) {
const subPropertiesDiff = getSubPropertiesDiff(
previousValue,
nextValue
);
return diff.push({
property: nextProperty,
previousValue,
currentValue: nextValue,
status: getPropertyStatus(subPropertiesDiff),
subPropertiesDiff
});
}
return diff.push({
property: nextProperty,
previousValue,
currentValue: nextValue,
status: getValueStatus(previousValue, nextValue)
});
});
const deletedProperties = getDeletedProperties(prevData, nextData);
if (deletedProperties) {
deletedProperties.forEach((deletedProperty) => {
diff.push({
property: deletedProperty.property,
previousValue: deletedProperty.value,
currentValue: void 0,
status: STATUS.DELETED
});
});
}
return {
type: "object",
status: getObjectStatus(diff),
diff
};
}
// src/list-diff.ts
function formatSingleListDiff(listData, status) {
return {
type: "list",
status,
diff: listData.map((data, i) => ({
value: data,
prevIndex: status === STATUS.ADDED ? null : i,
newIndex: status === STATUS.ADDED ? i : null,
indexDiff: null,
status
}))
};
}
function getListStatus(listDiff) {
return listDiff.some((value) => value.status !== STATUS.EQUAL) ? STATUS.UPDATED : STATUS.EQUAL;
}
var getListDiff = (prevList, nextList) => {
if (!prevList && !nextList) {
return {
type: "list",
status: STATUS.EQUAL,
diff: []
};
}
if (!prevList) {
return formatSingleListDiff(nextList, STATUS.ADDED);
}
if (!nextList) {
return formatSingleListDiff(prevList, STATUS.DELETED);
}
const diff = [];
nextList.forEach((nextValue, i) => {
const prevIndex = prevList.findIndex(
(prevValue) => isEqual(prevValue, nextValue)
);
const indexDiff = prevIndex === -1 ? null : i - prevIndex;
if (indexDiff === 0) {
return diff.push({
value: nextValue,
prevIndex,
newIndex: i,
indexDiff,
status: STATUS.EQUAL
});
}
if (prevIndex === -1) {
return diff.push({
value: nextValue,
prevIndex: null,
newIndex: i,
indexDiff,
status: STATUS.ADDED
});
}
return diff.push({
value: nextValue,
prevIndex,
newIndex: i,
indexDiff,
status: STATUS.MOVED
});
});
prevList.forEach((prevValue, i) => {
if (!nextList.some((nextValue) => isEqual(nextValue, prevValue))) {
return diff.splice(i, 0, {
value: prevValue,
prevIndex: i,
newIndex: null,
indexDiff: null,
status: STATUS.DELETED
});
}
});
return {
type: "list",
status: getListStatus(diff),
diff
};
};
exports.getListDiff = getListDiff;
exports.getObjectDiff = getObjectDiff;
exports.isEqual = isEqual;
exports.isObject = isObject;