Как я могу получить список различий между двумя графическими объектами JavaScript?
Я хочу получить список всех различий между двумя объектными графами JavaScript с именами свойств и значениями, в которых происходят дельта.
Для чего это необходимо, эти объекты обычно извлекаются с сервера как JSON и обычно составляют не более нескольких слоев (т.е. это может быть массив объектов, которые сами имеют данные, а затем массивы с другими объектами данных).
Я хочу не только увидеть изменения основных свойств, но и различия в количестве членов массива и т.д. и т.д.
Если я не получу ответа, я, вероятно, в конечном итоге напишу это сам, но надеюсь, что кто-то уже сделал эту работу или узнал кого-то, кто имеет.
EDIT: эти объекты, как правило, очень близки по структуре друг к другу, поэтому мы не говорим об объектах, которые совершенно отличаются друг от друга, но могут иметь 3 или 4 дельта.
Ответы
Ответ 1
После рассмотрения существующих ответов я заметил, что библиотека https://github.com/flitbit/diff еще не указана в качестве решения.
Из моих исследований эта библиотека, по-видимому, является лучшей с точки зрения активного развития, вкладов и вилок для решения проблемы различных объектов. Это очень удобно для создания diff на стороне сервера и передачи клиенту только измененных битов.
Ответ 2
Вот частичное, наивное решение моей проблемы - я обновлю это, когда буду развивать его.
function findDifferences(objectA, objectB) {
var propertyChanges = [];
var objectGraphPath = ["this"];
(function(a, b) {
if(a.constructor == Array) {
// BIG assumptions here: That both arrays are same length, that
// the members of those arrays are _essentially_ the same, and
// that those array members are in the same order...
for(var i = 0; i < a.length; i++) {
objectGraphPath.push("[" + i.toString() + "]");
arguments.callee(a[i], b[i]);
objectGraphPath.pop();
}
} else if(a.constructor == Object || (a.constructor != Number &&
a.constructor != String && a.constructor != Date &&
a.constructor != RegExp && a.constructor != Function &&
a.constructor != Boolean)) {
// we can safely assume that the objects have the
// same property lists, else why compare them?
for(var property in a) {
objectGraphPath.push(("." + property));
if(a[property].constructor != Function) {
arguments.callee(a[property], b[property]);
}
objectGraphPath.pop();
}
} else if(a.constructor != Function) { // filter out functions
if(a != b) {
propertyChanges.push({ "Property": objectGraphPath.join(""), "ObjectA": a, "ObjectB": b });
}
}
})(objectA, objectB);
return propertyChanges;
}
И вот пример того, как он будет использоваться, и данные, которые он будет предоставлять (пожалуйста, извините длинный пример, но я хочу использовать что-то относительно нетривиальное):
var person1 = {
FirstName : "John",
LastName : "Doh",
Age : 30,
EMailAddresses : [
"[email protected]",
"[email protected]"
],
Children : [
{
FirstName : "Sara",
LastName : "Doe",
Age : 2
}, {
FirstName : "Beth",
LastName : "Doe",
Age : 5
}
]
};
var person2 = {
FirstName : "John",
LastName : "Doe",
Age : 33,
EMailAddresses : [
"[email protected]",
"[email protected]"
],
Children : [
{
FirstName : "Sara",
LastName : "Doe",
Age : 3
}, {
FirstName : "Bethany",
LastName : "Doe",
Age : 5
}
]
};
var differences = findDifferences(person1, person2);
На этом этапе массив differences
будет выглядеть, если вы сериализовали его в JSON:
[
{
"Property":"this.LastName",
"ObjectA":"Doh",
"ObjectB":"Doe"
}, {
"Property":"this.Age",
"ObjectA":30,
"ObjectB":33
}, {
"Property":"this.EMailAddresses[1]",
"ObjectA":"[email protected]",
"ObjectB":"[email protected]"
}, {
"Property":"this.Children[0].Age",
"ObjectA":2,
"ObjectB":3
}, {
"Property":"this.Children[1].FirstName",
"ObjectA":"Beth",
"ObjectB":"Bethany"
}
]
Значение this
в значении Property
относится к корню объекта, который был сопоставлен. Итак, это решение еще не совсем то, что мне нужно, но это довольно близко.
Надеюсь, это полезно кому-то там, и если у вас есть предложения по улучшению, я все уши; Я написал это очень поздно прошлой ночью (то есть рано утром), и могут быть вещи, которые я полностью игнорирую.
Спасибо.
Ответ 3
Существует библиотека github.com/NV/objectDiff.js, которая позволяет вам это сделать. На своей демонстрационной странице вы можете увидеть разницу между двумя объектами javascript.
Ответ 4
Вы также можете попробовать rus-diff https://github.com/mirek/node-rus-diff, который генерирует совместимость с MongoDB (переименование/удаление/установка).
Для объектов вашего примера:
var person1 = {
FirstName: "John",
LastName: "Doh",
Age: 30,
EMailAddresses: ["[email protected]", "[email protected]"],
Children: [
{
FirstName: "Sara",
LastName: "Doe",
Age: 2
}, {
FirstName: "Beth",
LastName: "Doe",
Age: 5
}
]
};
var person2 = {
FirstName: "John",
LastName: "Doe",
Age: 33,
EMailAddresses: ["[email protected]", "[email protected]"],
Children: [
{
FirstName: "Sara",
LastName: "Doe",
Age: 3
}, {
FirstName: "Bethany",
LastName: "Doe",
Age: 5
}
]
};
var rusDiff = require('rus-diff').rusDiff
console.log(rusDiff(person1, person2))
Он генерирует список наборов:
{ '$set':
{ 'Age': 33,
'Children.0.Age': 3,
'Children.1.FirstName': 'Bethany',
'EMailAddresses.1': '[email protected]',
'LastName': 'Doe' } }
Ответ 5
Решение 1
Используйте JSON.stringify(obj), чтобы получить строковое представление объектов, которые вы хотите сравнить. Сохраните строку в файле. Для сравнения текстовых файлов используйте любой инструмент просмотра различий.
Примечание. JSON.stringify игнорирует свойства, указывающие на определения функций.
Решение 2
Это может сделать то, что вы хотите с некоторой модификацией, это модифицированная версия функции _.isEqual(http://documentcloud.github.com/underscore/). Не стесняйтесь предлагать любые изменения! Я написал его, чтобы выяснить, где происходит первое различие между двумя объектами.
// Given two objects find the first key or value not matching, algorithm is a
// inspired by of _.isEqual.
function diffObjects(a, b) {
console.info("---> diffObjects", {"a": a, "b": b});
// Check object identity.
if (a === b) return true;
// Different types?
var atype = typeof(a), btype = typeof(b);
if (atype != btype) {
console.info("Type mismatch:", {"a": a, "b": b});
return false;
};
// Basic equality test (watch out for coercions).
if (a == b) return true;
// One is falsy and the other truthy.
if ((!a && b) || (a && !b)) {
console.info("One is falsy and the other truthy:", {"a": a, "b": b});
return false;
}
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// One of them implements an isEqual()?
if (a.isEqual) return a.isEqual(b);
// Check dates' integer values.
if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
// Both are NaN?
if (_.isNaN(a) && _.isNaN(b)) {
console.info("Both are NaN?:", {"a": a, "b": b});
return false;
}
// Compare regular expressions.
if (_.isRegExp(a) && _.isRegExp(b))
return a.source === b.source &&
a.global === b.global &&
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline;
// If a is not an object by this point, we can't handle it.
if (atype !== 'object') {
console.info("a is not an object:", {"a": a});
return false;
}
// Check for different array lengths before comparing contents.
if (a.length && (a.length !== b.length)) {
console.info("Arrays are of different length:", {"a": a, "b": b});
return false;
}
// Nothing else worked, deep compare the contents.
var aKeys = _.keys(a), bKeys = _.keys(b);
// Different object sizes?
if (aKeys.length != bKeys.length) {
console.info("Different object sizes:", {"a": a, "b": b});
return false;
}
// Recursive comparison of contents.
for (var key in a) if (!(key in b) || !diffObjects(a[key], b[key])) return false;
return true;
};
Ответ 6
Этот script также имеет версию NPM, если вы используете NodeJS. https://github.com/NV/objectDiff.js
Радуйтесь.
Ответ 7
Недавно я написал модуль для этого, потому что меня не устраивало множество различных модулей, которые я нашел (я перечислил кучу самых популярных модулей и почему они не были приемлемы в readme моего модуля). Его называют odiff
: https://github.com/Tixit/odiff. Вот пример:
var a = [{a:1,b:2,c:3}, {x:1,y: 2, z:3}, {w:9,q:8,r:7}]
var b = [{a:1,b:2,c:3},{t:4,y:5,u:6},{x:1,y:'3',z:3},{t:9,y:9,u:9},{w:9,q:8,r:7}]
var diffs = odiff(a,b)
/* diffs now contains:
[{type: 'add', path:[], index: 2, vals: [{t:9,y:9,u:9}]},
{type: 'set', path:[1,'y'], val: '3'},
{type: 'add', path:[], index: 1, vals: [{t:4,y:5,u:6}]}
]
*/
Ответ 8
Ни одна из найденных библиотек не была достаточной, поэтому я написал собственный AngularJS factory. Он сравнивает объекты в обоих направлениях и возвращает только разницу в пределах одной и той же структуры.
/**
* Diff
* Original author: Danny Coulombe
* Creation date: 2016-05-18
*
* Work with objects to find their differences.
*/
controllers.factory('diff', [function() {
var factory = {
/**
* Compare the original object with the modified one and return their differences.
*
* @param original: Object
* @param modified: Object
*
* @return Object
*/
getDifferences: function(original, modified) {
var type = modified.constructor === Array ? [] : {};
var result = angular.copy(type);
var comparisons = [[original, modified, 1], [modified, original, 0]];
comparisons.forEach(function(comparison) {
angular.forEach(comparison[0], function(value, key) {
if(result[key] === undefined) {
if(comparison[1][key] !== undefined && value !== null && comparison[1][key] !== null && [Object, Array].indexOf(comparison[1][key].constructor) !== -1) {
result[key] = factory.getDifferences(value, comparison[1][key]);
}
else if(comparison[1][key] !== value) {
result[key] = comparison[comparison[2]][key];
}
if(angular.equals(type, result[key])
|| result[key] === undefined
|| (
comparison[0][key] !== undefined
&& result[key] !== null
&& comparison[0][key] !== null
&& comparison[0][key].length === comparison[1][key].length
&& result[key].length === 0
)) {
delete result[key];
}
}
});
});
return result;
}
};
return factory;
}]);
Ответ 9
var d = {
FirstName : "John",
LastName : "Doh",
Age : 30,
EMailAddresses : [
"[email protected]",
"[email protected]"
],
Children : [
{
FirstName : "Sara",
LastName : "Doe",
Age : 2
}, {
FirstName : "Beth",
LastName : "Doe",
Age : 5
}
]
};
var f = {
FirstName : "John",
LastName : "Doe",
Age : 33,
EMailAddresses : [
"[email protected]",
"[email protected]"
],
Children : [
{
FirstName : "Sara",
LastName : "Doe",
Age : 3
}, {
FirstName : "Bethany",
LastName : "Doe",
Age : 5
}
]
};
resultobj = []
function comp_obj(t1,t2){
flag = 1;
key1_arr = Object.keys(t1)
key2_arr = Object.keys(t2)
if(key1_arr.length == key2_arr.length){
for(key1 in t1){
ty1 = Object.prototype.toString.call(t1[key1])
ty2 = Object.prototype.toString.call(t2[key1])
if(ty1 == ty2) {
if(ty1 == '[object String]' || ty1 == '[object Number]' ){
if(t2[key1] != t1[key1]){
flag = 0;
break;
}
}else if(ty1 == '[object Array]'){
var result = comp_arr(t1[key1],t2[key1]);
console.log(ty1,ty2)
if(!result)
flag = 0;
}else if(ty1 == '[object Object]'){
var result = comp_obj(t1[key1],t2[key1])
if(!result)
flag = 0;
}
}else{
flag = 0;
break;
}
}
}else{
flag = 0;
}
if(flag)
return true
else
return false;
}
function comp_arr(a,b){
flag = 1;
if(a.length == b.length ){
for(var i=0,l=a.length;i<l;i++){
type1 = Object.prototype.toString.call(a[i])
type2 = Object.prototype.toString.call(b[i])
if(type1 == type2) {
if(type1 == '[object String]' || type1 == '[object Number]' ){
if( a[i] != b[i]){
flag = 0;
break;
}
}else if(type1 == '[object Array]'){
var result = comp_arr(a[i],b[i]);
if(!result)
flag = 0;
}else if(type1 == '[object Object]'){
var result = comp_obj(a[i],b[i])
if(!result)
flag = 0;
}
}else{
flag = 0;
break;
}
}
}else
flag = 0;
if(flag)
return true
else
return false;
}
function create(t,attr,parent_node,innerdata){
var dom = document.createElement(t)
for(key in attr){
dom.setAttribute(key,attr[key])
}
dom.innerHTML = innerdata;
parent_node.appendChild(dom)
return dom;
}
window.onload = function () {
for(key in f){
if(!(key in d))
resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
type1 = Object.prototype.toString.call(f[key])
type2 = Object.prototype.toString.call(d[key])
if(type1 == type2){
if(type1 == '[object String]' || type1 == '[object Number]' ){
if(f[key] != d[key])
resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
}else if(type1 == '[object Array]'){
var result = comp_arr(f[key],d[key]);
if(!result)
resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
}else if(type1 == '[object Object]'){
var result = comp_obj(f[key],d[key])
if(!result)
resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
}
}else
resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
}
var tb = document.getElementById('diff');
var s1 = document.getElementById('source1');
var s2 = document.getElementById('source2');
s1.innerHTML = 'Object 1 :'+ JSON.stringify(f)
s2.innerHTML = 'Object 2 :'+JSON.stringify(d)
resultobj.forEach(function(data,i){
tr_dom = create('tr',{},tb,'')
no = create('td',{},tr_dom,i+1)
Object.keys(data).forEach(function(tr){
td_dom = create('td',{},tr_dom,data[tr])
})
})
}
<html>
<body>
<p id="source1"> </p>
<p id="source2"> </p>
<p id="source7"> DIFFERENCE TABLE</p>
<table border=''>
<thead>
<th>S.no</th>
<th>Name Of the Key</th>
<th>Object1 Value</th>
<th>Object2 Value</th>
</thead>
<tbody id="diff">
</tbody>
</table>
</body>
</html>