Мне нужно реорганизовать мою программу, чтобы взять JSON obj, сохранить его где-нибудь, сделать кучу изменений, а затем сравнить два объекта, чтобы увидеть, что было изменено, удалено и добавлено.
Я не уверен в том, как это сделать в JS, так может ли кто-нибудь посоветовать сделать это в Angular (часть сравнения объектов)? В противном случае мне придется внести массу изменений в способ запуска/запуска моей программы из бэкэнд. Цените любую помощь.
Единственной встроенной операцией для сравнения объектов являются операторы равенства ==
/===
, которые используют ссылочное равенство: A есть B, а не A равно B.
Вам нужен список описателей изменений, описывающих разницу между двумя объектами.
Как указано в комментариях, это потребует рекурсивного обхода объектов, используя смесь контрольных и бытовых проверок.
Следующий алгоритм - это быстрая реализация идеи. Объекты перемещаются, и их изменения описываются списком объектов. Как и сами объекты, изменения вложены, но имеют уникальный идентификатор, основанный на их местоположении внутри объекта (чтобы его можно было сгладить).
function diff(a, b, namespace) {
namespace = (namespace || '') + '.';
var keysInA = Object.keys(a),
keysInB = Object.keys(b);
var diffA = keysInA.reduce(function(changes, key) {
var ns = namespace + key;
if(typeof b[key] == 'undefined') {
return changes.concat([{ type: 'DELETED', id: ns }]);
}
if(a[key] !== b[key]) {
return changes.concat([{ type: 'CHANGED', id: ns }]);
}
if(typeof a[key] == b[key] == 'object') {
return diff(a[key], b[key], ns);
}
return changes;
}, []);
var diffB = keysInB.reduce(function(changes, key) {
var ns = namespace + key;
if(typeof a[key] == 'undefined') {
return changes.concat([{ type: 'ADDED', id: ns }]);
}
return changes;
}, []);
return diffA.concat(diffB);
}
Например, мы берем исходное состояние объекта.
var a = { a: 1, b: 2, c: 3 };
И новое государство.
var b = { a: 2, c: 3, d: 5 };
Затем запустите их с помощью функции diff
.
diff(a, b);
Он возвращает список изменений.
[
{ id: '.a', type: 'CHANGED' },
{ id: '.b', type: 'DELETED' },
{ id: '.d', type: 'ADDED' }
]
Очевидно, вам придется адаптировать этот алгоритм, чтобы он соответствовал вашим критериям для того, что представляет собой изменение. Возможно, вы захотите взглянуть на глубокое равенство, а не на сравнение ссылок.
angular.equals()
уже встроена в angular.equals()
Я добавлю свою реализацию предложения Dan здесь, если это поможет кому-то, кто хочет увидеть фактическую реализацию:
var propertiesToIgnore = ['.indexesTracked', '.notInSyncWithDb', '.date', '.details.updatedAt', '.autoLoadLocalStorage', '.deletionQueue']; //these are extraneous properties added to project that should not be represented in interface (and not tracked)
var keysToIgnore = ['addAllDatacuts', 'datacutNames']; // this just looks at the property rather than the above which matches from the project root
function diff(a, b, namespace, firstCall) {
namespace = firstCall ? (namespace || '') : (namespace || '') + '.';
var keysInA = Object.keys(a),
keysInB = Object.keys(b);
var diffA = keysInA.reduce(function(changes, key) {
var ns = namespace + key;
if (propertiesToIgnore.indexOf(ns) !== -1 || keysToIgnore.indexOf(key) !== -1) {
return changes;
}
if (key == '$$hashKey') {
return changes;
}
if (angular.equals(a[key], b[key])) { // whole chain is equal so I do not need to iterate over this branch
return changes;
}
if (typeof b[key] == 'undefined') {
return changes.concat([{ type: 'DELETED', id: ns }]);
}
if (a[key] !== b[key] && (typeof b[key] !== 'object' || typeof a[key] !== 'object')) {
return changes.concat([{ type: 'UPDATED', id: ns }]);
}
if (typeof b[key] === 'object' && typeof a[key] === 'object') {
return changes.concat(diff(a[key], b[key], ns));
}
if (a[key] === null || b[key] === null) { // avoids values that are null as js considers null an object
return changes;
}
if(typeof a[key] == 'object' && typeof b[key] == 'object' && typeof a[key].getMonth !== 'function' && typeof b[key].getMonth !== 'function') { // last part necessary to make sure it is not a date object
return diff(a[key], b[key], ns);
}
return changes;
}, []);
var diffB = keysInB.reduce(function(changes, key) {
var ns = namespace + key;
if (propertiesToIgnore.indexOf(ns) !== -1 || keysToIgnore.indexOf(key) !== -1) {
return changes;
}
if (key == '$$hashKey') {
return changes;
}
if (typeof a[key] == 'undefined') {
return changes.concat([{ type: 'ADDED', id: ns }]);
}
return changes;
}, []);
return diffA.concat(diffB);
}
$scope.changes = diff(dbData, $scope.project, '');