UNPKG

recursive-diff

Version:

Find diff between any two variables where variables be any valid JavaScript data type like string, numeric, array or object

137 lines (124 loc) 3.66 kB
const { types, iterableTypes, errors } = require('./config'); const utils = require('./utils'); const checkType = { [types.NUMBER]: utils.isNumber, [types.BOOLEAN]: utils.isBoolean, [types.STRING]: utils.isString, [types.DATE]: utils.isDate, [types.UNDEFINED]: utils.isUndefined, [types.NULL]: utils.isNull, [types.ARRAY]: utils.isArray, [types.MAP]: utils.isMap, [types.SET]: utils.isSet, [types.ITERABLE_OBJECT]: utils.isIterableObject, }; const checkEqualityForComplexTypes = { [types.DATE]: utils.areDatesEqual, }; function getType(x) { const keys = Object.keys(checkType); let type = types.DEFAULT; for (let i = 0; i < keys.length; i += 1) { if (checkType[keys[i]](x)) { type = keys[i]; break; } } return type; } function isTraversalNeeded(type1, type2) { return type1 === type2 && iterableTypes.indexOf(type1) >= 0; } function areEqual(x, y, type1, type2) { if (type1 !== type2) { return false; } return checkEqualityForComplexTypes[type1] ? checkEqualityForComplexTypes[type1](x, y) : x === y; } function computeOp(x, y, type1, type2) { let op; if (type1 === types.UNDEFINED && type2 !== types.UNDEFINED) { op = 'add'; } else if (type1 !== types.UNDEFINED && type2 === types.UNDEFINED) { op = 'delete'; } else if (!(areEqual(x, y, type1, type2))) { op = 'update'; } else { utils.noop(); } return op; } function getKeys(x, y, type) { if (type === types.ARRAY) { const keys = x.length > y.length ? new Array(x.length) : new Array(y.length); keys.fill(0); return new Set(keys.map((_, i) => i)); } return new Set(Object.keys(x).concat(Object.keys(y))); } function makeDiff(x, y, op, path, keepOldVal) { const diffOb = { op, path, }; if (op === 'add' || op === 'update') { diffOb.val = y; } if (keepOldVal && op !== 'add') { diffOb.oldVal = x; } return diffOb; } function privateGetDiff(x, y, keepOldVal, path, diff) { const type1 = getType(x); const type2 = getType(y); const currPath = path || []; const currDiff = diff || []; if (isTraversalNeeded(type1, type2)) { const iterator = getKeys(x, y, type1).values(); let { value, done } = iterator.next(); while (!done) { if (!(Object.prototype.hasOwnProperty.call(x, value))) { currDiff.push(makeDiff(x[value], y[value], 'add', currPath.concat(value), keepOldVal)); } else if (!(Object.prototype.hasOwnProperty.call(y, value))) { currDiff.push(makeDiff(x[value], y[value], 'delete', currPath.concat(value), keepOldVal)); } else { privateGetDiff(x[value], y[value], keepOldVal, currPath.concat(value), currDiff); } const curr = iterator.next(); value = curr.value; done = curr.done; } } else { const op = computeOp(x, y, type1, type2); if (op != null) { currDiff.push(makeDiff(x, y, op, path, keepOldVal)); } } return currDiff; } const opHandlers = { add: utils.setValueByPath, update: utils.setValueByPath, delete: utils.deleteValueByPath, }; function privateApplyDiff(x, diff, visitorCallback) { if (!(diff instanceof Array)) throw new Error(errors.INVALID_DIFF_FORMAT); let y = x; diff.forEach((diffItem) => { const { op, val, path } = diffItem; if (!opHandlers[op]) { throw new Error(errors.INVALID_DIFF_OP); } y = opHandlers[op](y, path, val, visitorCallback); }); return y; } module.exports = { getDiff(x, y, keepOldValInDiff = false) { return privateGetDiff(x, y, keepOldValInDiff); }, applyDiff(x, diff, visitorCallback) { return privateApplyDiff(x, diff, visitorCallback); }, };