This detects the difference between two objects (or revisions):
var A = {A: 10, B: 20, C: 30, D: 90, BB: 5, CC: 4, AA: 2};
var B = { B: 22, C: 30, L: 4, KK: 444, A:15};
var difference = diff(A, B);
console.log(difference);
document.write("<pre>"+JSON.stringify(difference, 0, 3)+"</pre>");
function diff(a, b) {
var result = {removed: "", changed: "", created: "", notChanged: ""};
var str1 = JSON.stringify(a);
str1 = str1.slice(1, str1.length - 1);
var arr1 = str1.split(',');
var str2 = JSON.stringify(b);
str2 = str2.slice(1, str2.length - 1);
var arr2 = str2.split(',');
var keys1 = Object.keys(a);
var keys2 = Object.keys(b);
while (arr1.length || arr2.length) {
var key1 = keys1[0];
var key2 = keys2[0];
var findIndex = keys2.indexOf(key1);
var findIndex2 = keys1.indexOf(key2);
if (findIndex >= 0 && findIndex2 >= 0) {
if (a[key1] == b[keys2[findIndex]]) {
arr2.splice(findIndex, 1);
keys2.splice(findIndex, 1);
keys1.shift();
result.notChanged += (result.notChanged && ", ") + arr1.shift();
} else {
arr1.shift();
keys1.shift();
keys2.splice(findIndex, 1);
result.changed += (result.changed && ", ") + arr2.splice(findIndex, 1);
}
} else {
if (findIndex === -1 && keys1.length) {
keys1.shift();
result.removed += (result.removed && ", ") + arr1.shift();
}
if (findIndex2 === -1 && keys2.length) {
keys2.shift();
result.created += (result.created && ", ") + arr2.shift();
}
}
}
result.removed = JSON.parse("{" + result.removed + "}");
result.notChanged = JSON.parse("{" + result.notChanged + "}");
result.changed = JSON.parse("{" + result.changed + "}");
result.created = JSON.parse("{" + result.created + "}");
return result;
}
Is there anything wrong with my snippet? Do you know of another great way?
2 Answers 2
You certainly seem to be going a pretty long detour in some cases.
What worries me most is that you look at stringified JSON. Just look at the objects. If you try to manually parse JSON by splitting on commas, you risk splitting on commas in strings, and all sorts of other nonsense.
You really don't need raw JSON strings for anything here (except perhaps for printing the result in a readable way)
Just iterate the objects with for...in
, and use hasOwnProperty
to check for properties.
For instance:
var A = {A: 10, B: 20, C: 30, D: 90, BB: 5, CC: 4, AA: 2};
var B = { B: 22, C: 30, L: 4, KK: 444, A:15};
var difference = diff(A, B);
document.write("<pre>"+JSON.stringify(difference, 0, 3)+"</pre>");
function diff(a, b) {
var result = {
removed: {},
changed: {},
created: {},
notChanged: {}
};
// check for changed/unchanged/removed keys
for(var aKey in a) {
if(!a.hasOwnProperty(aKey)) continue; // skip inherited properties
if(b.hasOwnProperty(aKey)) {
// check for changed/unchanged values
if(b[aKey] === a[aKey]) {
result.notChanged[aKey] = a[aKey]; // no change between a and b
} else {
result.changed[aKey] = [a[aKey], b[aKey]]; // get before/after values
}
} else {
result.removed[aKey] = a[aKey]; // key wasn't in b, so it's been removed
}
}
// do the reverse to check for added keys
for(var bKey in a) {
if(!b.hasOwnProperty(bKey)) continue;
if(!a.hasOwnProperty(bKey)) {
result.created[bKey] = b[bKey];
}
}
return result;
}
-
\$\begingroup\$ Thanks. Yeah, you are right. I just tried to avoid second loop. Do you know another way? \$\endgroup\$Sherali Turdiyev– Sherali Turdiyev2015年10月20日 18:21:03 +00:00Commented Oct 20, 2015 at 18:21
-
\$\begingroup\$ Perhaps, there are another library or plugins \$\endgroup\$Sherali Turdiyev– Sherali Turdiyev2015年10月20日 18:22:18 +00:00Commented Oct 20, 2015 at 18:22
-
1\$\begingroup\$ @SheraliTurdiyev I'm sure you can find several libraries with this sort of behavior, but I don't know any off hand. And there's no good way to get rid of the 2nd loop. It kinda has to be there. But this code is still a lot more efficient than the original; each
indexOf
in the original code is basically a loop too, so that's 1 outer loop plus 2 nested loops, versus two plain loops here). So it's not like it's a problem to have two loops. \$\endgroup\$Flambino– Flambino2015年10月20日 18:43:43 +00:00Commented Oct 20, 2015 at 18:43
You're doing a lot of extra work involving strings which isn't necessary; also be wary that not all things translate to JSON (NaN, Infinity). The other thing that comes to mind is that using indexOf
might require linear time to search in an array - you can avoid that altogether by just looking up the property on the object itself.
When checking if a.x === b.x
you should use hasOwnProperty
- otherwise you'll get false positives if any one of them is set to undefined
. And then there's the infamous NaN
which even if it exists on both objects on the same property it'll evaluate to false
under strict equality. That's why you should use Object.is
.
I also removed the serialization away from the function - the diff method should be concerned with diffing and not pretty printing.
var A = { A: 10, B: 20, C: 30, D: 90, BB: 5, CC: 4, AA: 2 };
var B = { B: 22, C: 30, L: 4, KK: 444, A: 15 };
var difference = diff(A, B)
console.log(difference);
document.write("<pre>" + JSON.stringify(difference, 0, 3) + "</pre>");
function diff(a, b, equals) {
equals = equals || Object.is;
var changed = {};
var notChanged = {};
var created = {};
var removed = {};
Object.keys(a).forEach(function (key) {
if (b.hasOwnProperty(key)) {
if (equals(a[key], b[key])) {
notChanged[key] = b[key];
} else {
console.log(key);
changed[key] = b[key];
}
} else {
removed[key] = a[key];
}
});
Object.keys(b).forEach(function (key) {
if (!a.hasOwnProperty(key)) {
created[key] = b[key];
}
});
return {
changed: changed,
notChanged: notChanged,
created: created,
removed: removed
};
}
-
\$\begingroup\$ Thanks, do you know another way, plugins, libraries? \$\endgroup\$Sherali Turdiyev– Sherali Turdiyev2015年10月20日 19:02:06 +00:00Commented Oct 20, 2015 at 19:02