UNPKG

diff-json

Version:

Generates diffs of javascript objects.

328 lines (326 loc) 11 kB
// Generated by CoffeeScript 1.12.7 (function() { (function() { var _difference, _find, _intersection, _keyBy, addKeyValue, applyArrayChange, applyBranchChange, applyLeafChange, changeset, compare, compareArray, compareObject, comparePrimitives, convertArrayToObj, exports, getKey, getTypeOfObj, indexOfItemInArray, isEmbeddedKey, modifyKeyValue, parseEmbeddedKeyValue, removeKey, revertArrayChange, revertBranchChange, revertLeafChange; changeset = { VERSION: '0.1.4' }; if (typeof module === 'object' && module.exports) { _intersection = require('lodash.intersection'); _difference = require('lodash.difference'); _keyBy = require('lodash.keyby'); _find = require('lodash.find'); module.exports = exports = changeset; } else { this.changeset = changeset; } getTypeOfObj = function(obj) { if (typeof obj === 'undefined') { return 'undefined'; } if (obj === null) { return null; } return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1]; }; getKey = function(path) { var ref; return (ref = path[path.length - 1]) != null ? ref : '$root'; }; compare = function(oldObj, newObj, path, embededObjKeys, keyPath) { var changes, diffs, typeOfNewObj, typeOfOldObj; changes = []; typeOfOldObj = getTypeOfObj(oldObj); typeOfNewObj = getTypeOfObj(newObj); if (typeOfOldObj !== typeOfNewObj) { changes.push({ type: changeset.op.REMOVE, key: getKey(path), value: oldObj }); changes.push({ type: changeset.op.ADD, key: getKey(path), value: newObj }); return changes; } switch (typeOfOldObj) { case 'Date': changes = changes.concat(comparePrimitives(oldObj.getTime(), newObj.getTime(), path)); break; case 'Object': diffs = compareObject(oldObj, newObj, path, embededObjKeys, keyPath); if (diffs.length) { if (path.length) { changes.push({ type: changeset.op.UPDATE, key: getKey(path), changes: diffs }); } else { changes = changes.concat(diffs); } } break; case 'Array': changes = changes.concat(compareArray(oldObj, newObj, path, embededObjKeys, keyPath)); break; case 'Function': break; default: changes = changes.concat(comparePrimitives(oldObj, newObj, path)); } return changes; }; compareObject = function(oldObj, newObj, path, embededObjKeys, keyPath, skipPath) { var addedKeys, changes, deletedKeys, diffs, i, intersectionKeys, j, k, l, len, len1, len2, newKeyPath, newObjKeys, newPath, oldObjKeys; if (skipPath == null) { skipPath = false; } changes = []; oldObjKeys = Object.keys(oldObj); newObjKeys = Object.keys(newObj); intersectionKeys = _intersection(oldObjKeys, newObjKeys); for (i = 0, len = intersectionKeys.length; i < len; i++) { k = intersectionKeys[i]; newPath = path.concat([k]); newKeyPath = skipPath ? keyPath : keyPath.concat([k]); diffs = compare(oldObj[k], newObj[k], newPath, embededObjKeys, newKeyPath); if (diffs.length) { changes = changes.concat(diffs); } } addedKeys = _difference(newObjKeys, oldObjKeys); for (j = 0, len1 = addedKeys.length; j < len1; j++) { k = addedKeys[j]; newPath = path.concat([k]); newKeyPath = skipPath ? keyPath : keyPath.concat([k]); changes.push({ type: changeset.op.ADD, key: getKey(newPath), value: newObj[k] }); } deletedKeys = _difference(oldObjKeys, newObjKeys); for (l = 0, len2 = deletedKeys.length; l < len2; l++) { k = deletedKeys[l]; newPath = path.concat([k]); newKeyPath = skipPath ? keyPath : keyPath.concat([k]); changes.push({ type: changeset.op.REMOVE, key: getKey(newPath), value: oldObj[k] }); } return changes; }; compareArray = function(oldObj, newObj, path, embededObjKeys, keyPath) { var diffs, indexedNewObj, indexedOldObj, ref, uniqKey; uniqKey = (ref = embededObjKeys != null ? embededObjKeys[keyPath.join('.')] : void 0) != null ? ref : '$index'; indexedOldObj = convertArrayToObj(oldObj, uniqKey); indexedNewObj = convertArrayToObj(newObj, uniqKey); diffs = compareObject(indexedOldObj, indexedNewObj, path, embededObjKeys, keyPath, true); if (diffs.length) { return [ { type: changeset.op.UPDATE, key: getKey(path), embededKey: uniqKey, changes: diffs } ]; } else { return []; } }; convertArrayToObj = function(arr, uniqKey) { var index, obj, value; obj = {}; if (uniqKey !== '$index') { obj = _keyBy(arr, uniqKey); } else { for (index in arr) { value = arr[index]; obj[index] = value; } } return obj; }; comparePrimitives = function(oldObj, newObj, path) { var changes; changes = []; if (oldObj !== newObj) { changes.push({ type: changeset.op.UPDATE, key: getKey(path), value: newObj, oldValue: oldObj }); } return changes; }; isEmbeddedKey = function(key) { return /\$.*=/gi.test(key); }; removeKey = function(obj, key, embededKey) { var index; if (Array.isArray(obj)) { if (embededKey !== '$index' || !obj[key]) { index = indexOfItemInArray(obj, embededKey, key); } return obj.splice(index != null ? index : key, 1); } else { return delete obj[key]; } }; indexOfItemInArray = function(arr, key, value) { var index, item; for (index in arr) { item = arr[index]; if (key === '$index') { if (item === value) { return index; } } else if (item[key] === value) { return index; } } return -1; }; modifyKeyValue = function(obj, key, value) { return obj[key] = value; }; addKeyValue = function(obj, key, value) { if (Array.isArray(obj)) { return obj.push(value); } else { return obj[key] = value; } }; parseEmbeddedKeyValue = function(key) { var uniqKey, value; uniqKey = key.substring(1, key.indexOf('=')); value = key.substring(key.indexOf('=') + 1); return { uniqKey: uniqKey, value: value }; }; applyLeafChange = function(obj, change, embededKey) { var key, type, value; type = change.type, key = change.key, value = change.value; switch (type) { case changeset.op.ADD: return addKeyValue(obj, key, value); case changeset.op.UPDATE: return modifyKeyValue(obj, key, value); case changeset.op.REMOVE: return removeKey(obj, key, embededKey); } }; applyArrayChange = function(arr, change) { var element, i, len, ref, results, subchange; ref = change.changes; results = []; for (i = 0, len = ref.length; i < len; i++) { subchange = ref[i]; if ((subchange.value != null) || subchange.type === changeset.op.REMOVE) { results.push(applyLeafChange(arr, subchange, change.embededKey)); } else { if (change.embededKey === '$index') { element = arr[+subchange.key]; } else { element = _find(arr, function(el) { return el[change.embededKey] === subchange.key; }); } results.push(changeset.applyChanges(element, subchange.changes)); } } return results; }; applyBranchChange = function(obj, change) { if (Array.isArray(obj)) { return applyArrayChange(obj, change); } else { return changeset.applyChanges(obj, change.changes); } }; revertLeafChange = function(obj, change, embededKey) { var key, oldValue, type, value; type = change.type, key = change.key, value = change.value, oldValue = change.oldValue; switch (type) { case changeset.op.ADD: return removeKey(obj, key, embededKey); case changeset.op.UPDATE: return modifyKeyValue(obj, key, oldValue); case changeset.op.REMOVE: return addKeyValue(obj, key, value); } }; revertArrayChange = function(arr, change) { var element, i, len, ref, results, subchange; ref = change.changes; results = []; for (i = 0, len = ref.length; i < len; i++) { subchange = ref[i]; if ((subchange.value != null) || subchange.type === changeset.op.REMOVE) { results.push(revertLeafChange(arr, subchange, change.embededKey)); } else { if (change.embededKey === '$index') { element = arr[+subchange.key]; } else { element = _find(arr, function(el) { return el[change.embededKey] === subchange.key; }); } results.push(changeset.revertChanges(element, subchange.changes)); } } return results; }; revertBranchChange = function(obj, change) { if (Array.isArray(obj)) { return revertArrayChange(obj, change); } else { return changeset.revertChanges(obj, change.changes); } }; changeset.diff = function(oldObj, newObj, embededObjKeys) { return compare(oldObj, newObj, [], embededObjKeys, []); }; changeset.applyChanges = function(obj, changesets) { var change, i, len, results; results = []; for (i = 0, len = changesets.length; i < len; i++) { change = changesets[i]; if ((change.value != null) || change.type === changeset.op.REMOVE) { results.push(applyLeafChange(obj, change, change.embededKey)); } else { results.push(applyBranchChange(obj[change.key], change)); } } return results; }; changeset.revertChanges = function(obj, changeset) { var change, i, len, ref, results; ref = changeset.reverse(); results = []; for (i = 0, len = ref.length; i < len; i++) { change = ref[i]; if (!change.changes) { results.push(revertLeafChange(obj, change)); } else { results.push(revertBranchChange(obj[change.key], change)); } } return results; }; changeset.op = { REMOVE: 'remove', ADD: 'add', UPDATE: 'update' }; })(); }).call(this);