array-comparisoncomparisoncomparison-tooldeep-diffdiffjson-diffnodejsobject-comparisonobject-diffobjectdiffobjectdifferencereactstreamingstreaming-datatypescript
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
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;
|
|
|