0
\$\begingroup\$

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?

\$\endgroup\$
1
\$\begingroup\$

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;
}

\$\endgroup\$
  • \$\begingroup\$ Thanks. Yeah, you are right. I just tried to avoid second loop. Do you know another way? \$\endgroup\$ – Sherali Turdiyev Oct 20 '15 at 18:21
  • \$\begingroup\$ Perhaps, there are another library or plugins \$\endgroup\$ – Sherali Turdiyev Oct 20 '15 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 Oct 20 '15 at 18:43
1
\$\begingroup\$

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
    };
}

\$\endgroup\$
  • \$\begingroup\$ Thanks, do you know another way, plugins, libraries? \$\endgroup\$ – Sherali Turdiyev Oct 20 '15 at 19:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.