object-difference-js
Version:
get deep difference between two JavaScript objects
396 lines (350 loc) • 12.4 kB
JavaScript
import anyToString from "./anyToString.js";
const VALUE_CREATED = "created",
VALUE_UPDATED = "updated",
VALUE_DELETED = "deleted",
NOT_COMPARABLE = "NA";
function _isBlank(t) {
return t === "" || t === 0 || t === undefined || t === null || t === {} || (Array.isArray(t) && t.length < 1);
}
function _isValue(t) {
return !_isBlank(t) && (typeof t === "boolean" || typeof t === "number" || typeof t === "string");
}
function _isDate(t) {
return !_isBlank(t) && t instanceof Date;
}
function _isArray(t) {
return !_isBlank(t) && Array.isArray(t);
}
function _isFunction(t) {
return typeof t === "function";
}
function _isSymbol(t) {
return typeof t === "symbol";
}
function _isObject(t) {
return !_isValue(t) && !_isBlank(t) && !_isDate(t) && !_isArray(t) && !_isFunction(t) && !_isSymbol(t);
}
function _getFullPropName(parentName, propertyName) {
return parentName ? `${parentName}.${propertyName}` : propertyName;
}
function _getArrayDiffence(parentName, propertyName, a1, a2) {
let diff = [];
if (a1.length === a2.length) {
for (let i = 0; i < a1.length; i++) {
if (JSON.stringify(a1[i]) === JSON.stringify(a2[i])) {
continue;
} else {
for (let i = 0; i < a1.length; i++) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), `[${i}]`, a1[i], a2[i])); // eslint-disable-line no-use-before-define
}
}
}
return diff;
}
if (a1.length > 30 || a2.length > 30) {
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_UPDATED,
from: `Array[${a1.length}]`,
to: `Array[${a2.length}]`
});
return diff;
}
function comparer(otherArray) {
return function (current) {
return (
otherArray.filter(function (other) {
return JSON.stringify(other) === JSON.stringify(current);
}).length === 0
);
};
}
let onlyInA1 = a1.filter(comparer(a2));
let onlyInA2 = a2.filter(comparer(a1));
for (let i = 0; i < onlyInA1.length; i++) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), `[${i}]`, onlyInA1[i], undefined)); // eslint-disable-line no-use-before-define
}
for (let i = 0; i < onlyInA2.length; i++) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), `[${i}]`, undefined, onlyInA2[i])); // eslint-disable-line no-use-before-define
}
return diff;
}
function _getArrayDiffenceByKey(parentName, propertyName, a1, a2, keyProp) {
let diff = [];
function comparer(otherArray) {
return function (current) {
return (
otherArray.filter(function (other) {
return other[keyProp] === current[keyProp];
}).length === 0
);
};
}
let onlyInA1 = a1.filter(comparer(a2));
let onlyInA2 = a2.filter(comparer(a1));
for (let i = 0; i < onlyInA1.length; i++) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), `[${keyProp}=${onlyInA1[i][keyProp]}]`, onlyInA1[i], undefined)); // eslint-disable-line no-use-before-define
}
for (let i = 0; i < onlyInA2.length; i++) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), `[${keyProp}=${onlyInA2[i][keyProp]}]`, undefined, onlyInA2[i])); // eslint-disable-line no-use-before-define
}
for (let i = 0; i < a1.length; i++) {
let a2Item = a2.find(x => {
return x[keyProp] === a1[i][keyProp];
});
if (a2Item) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), `[${keyProp}=${a1[i][keyProp]}]`, a1[i], a2Item)); // eslint-disable-line no-use-before-define
}
}
return diff;
}
function _GetObjectDifference(parentName, propertyName, valueFrom, valueTo, config = {}) {
let diff = [];
if (propertyName === "__ob__" || propertyName === "__proto__") {
return diff;
}
// case: 两个都是基础类型
if (_isValue(valueFrom) && _isValue(valueTo)) {
if (valueFrom == valueTo) { // eslint-disable-line
return diff;
}
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_UPDATED,
from: valueFrom,
to: valueTo
});
return diff;
}
// case: 排除有function的情况
if (_isFunction(valueFrom) || _isFunction(valueTo)) {
diff.push({
property: _getFullPropName(parentName, propertyName),
type: NOT_COMPARABLE,
from: anyToString(valueFrom),
to: anyToString(valueTo)
});
return diff;
}
// case: 其中一个是symbol
if (_isSymbol(valueFrom) || _isSymbol(valueTo)) {
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_UPDATED,
from: anyToString(valueFrom),
to: anyToString(valueTo)
});
return diff;
}
// case: 两个都是日期
if (_isDate(valueFrom) && _isDate(valueTo)) {
if (valueFrom.getTime() === valueTo.getTime()) {
return diff;
}
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_UPDATED,
from: anyToString(valueFrom),
to: anyToString(valueTo)
});
return diff;
}
// case: 一个是日期,另一个是值
if (_isDate(valueTo) && _isValue(valueFrom)) {
if (new Date(valueFrom).getTime() === valueTo.getTime()) {
return diff;
}
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_UPDATED,
from: anyToString(valueFrom),
to: anyToString(valueTo)
});
return diff;
}
if (_isDate(valueFrom) && _isValue(valueTo)) {
if (new Date(valueTo).getTime() === valueFrom.getTime()) {
return diff;
}
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_UPDATED,
from: anyToString(valueFrom),
to: anyToString(valueTo)
});
return diff;
}
// case: 两个都是Blank
if (_isBlank(valueFrom) && _isBlank(valueTo)) {
return diff;
}
// case: From是blank, To不是
if (_isBlank(valueFrom) && _isValue(valueTo)) {
if(!valueTo) { //eslint-disable-line
return diff;
}
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_CREATED,
from: "",
to: anyToString(valueTo)
});
return diff;
}
if (_isBlank(valueFrom) && _isDate(valueTo)) {
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_CREATED,
from: "",
to: anyToString(valueTo)
});
return diff;
}
if (_isBlank(valueFrom) && _isArray(valueTo)) {
for (let i = 0; i < valueTo.length; i++) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), `[${i}]`, undefined, valueTo[i]));
}
return diff;
}
if (_isBlank(valueFrom) && _isObject(valueTo)) {
Object.getOwnPropertyNames(valueTo).forEach(function (val, idx, array) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), val, undefined, valueTo[val]));
});
return diff;
}
// case: From不是blank, To是
if ((_isValue(valueFrom) || _isDate(valueFrom) || _isArray(valueFrom) || _isObject(valueFrom)) && _isBlank(valueTo)) {
if (!valueFrom) {
return diff;
}
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_DELETED,
from: "",
to: ""
});
return diff;
}
// case: From是值或日期,To是Array
if ((_isValue(valueFrom) || _isDate(valueFrom)) && _isArray(valueTo)) {
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_DELETED,
from: "",
to: ""
});
for (let i = 0; i < valueTo.length; i++) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), `[${i}]`, undefined, valueTo[i]));
}
return diff;
}
// case: From是值或日期,To是Object
if ((_isValue(valueFrom) || _isDate(valueFrom)) && _isObject(valueTo)) {
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_DELETED,
from: "",
to: ""
});
Object.getOwnPropertyNames(valueTo).forEach(function (val, idx, array) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), val, undefined, valueTo[val]));
});
return diff;
}
// case: From是Array,To是值或日期
if (_isArray(valueFrom) && (_isValue(valueTo) || _isDate(valueTo))) {
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_UPDATED,
from: `Array[${valueFrom.length}]`,
to: anyToString(valueTo)
});
return diff;
}
// case: From是Array,To是Array
if (_isArray(valueFrom) && _isArray(valueTo)) {
if (config.ArrayKeyProperty) {
// check whether Array element has the key
let missKeyProp = false;
for (let i = 0; i < valueFrom.length; i++) {
if (valueFrom[i][config.ArrayKeyProperty] === undefined) {
missKeyProp = true;
}
}
for (let i = 0; i < valueTo.length; i++) {
if (valueTo[i][config.ArrayKeyProperty] === undefined) {
missKeyProp = true;
}
}
if (!missKeyProp) {
diff = diff.concat(_getArrayDiffenceByKey(parentName, propertyName, valueFrom, valueTo, config.ArrayKeyProperty));
} else {
diff = diff.concat(_getArrayDiffence(parentName, propertyName, valueFrom, valueTo));
}
} else {
diff = diff.concat(_getArrayDiffence(parentName, propertyName, valueFrom, valueTo));
}
return diff;
}
// case: From是Array,To是Object
if (_isArray(valueFrom) && _isObject(valueTo)) {
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_DELETED,
from: `Array[${valueFrom.length}]`,
to: ""
});
Object.getOwnPropertyNames(valueTo).forEach(function (val, idx, array) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), val, undefined, valueTo[val]));
});
return diff;
}
// case: From是Object,To是值或日期
if (_isObject(valueFrom) && (_isValue(valueTo) || _isDate(valueTo))) {
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_UPDATED,
from: "{}",
to: anyToString(valueTo)
});
return diff;
}
// case: From是Object,To是Array
if (_isObject(valueFrom) && _isArray(valueTo)) {
diff.push({
property: _getFullPropName(parentName, propertyName),
type: VALUE_DELETED,
from: "{}",
to: anyToString(valueTo)
});
for (let i = 0; i < valueTo.length; i++) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), `[${i}]`, undefined, valueTo[i]));
}
return diff;
}
// case: From是Object,To是Object
if (_isObject(valueFrom) && _isObject(valueTo)) {
Object.getOwnPropertyNames(valueFrom).forEach(function (val, idx, array) {
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), val, valueFrom[val], valueTo[val]));
});
Object.getOwnPropertyNames(valueTo).forEach(function (val, idx, array) {
if (valueFrom[val] !== undefined) {
return;
}
diff = diff.concat(_GetObjectDifference(_getFullPropName(parentName, propertyName), val, undefined, valueTo[val]));
});
}
return diff;
}
/**
* Get deep difference between two objects. An empty array will be returned for two same objects comparing.
*
* @export
* @param {*} propertyName object name, as the root property name
* @param {*} valueFrom value comparing on the left
* @param {*} valueTo value comparing on the right
* @returns
*/
export function GetObjectDifference(objectName, valueFrom, valueTo, config = {}) {
return _GetObjectDifference("", objectName, valueFrom, valueTo, config);
}