Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery function to compute the difference between two JavaScript objects

I have a rich AJAX-based web application that uses JQuery + Knockout. I have a JQuery plugin that wraps my Knockout view models to expose utility methods like .reset(), .isDirty(), and so on.

I have a method called .setBaseline() that essentially takes a snapshot of the data model once it has been populated (via the mapping plugin). Then I can use this snapshot to quickly determine if the model has changed.

What I'm looking for is some kind of general purpose function that can return an object that represents the differences between two 2 JavaScript objects where one of the objects is considered to be the master.

For example, assume that this is my snapshot:

var snapShot = {
  name: "Joe",
  address: "123 Main Street",
  age: 30,
  favoriteColorPriority: {
     yellow: 1,
     pink: 2,
     blue: 3
  }
};

Then assume that the live data looks like this:

var liveData = {
    name: "Joseph",
    address: "123 Main Street",
    age: 30,
    favoriteColorPriority: {
        yellow: 1,
        pink: 3,
        blue: 2
    }
};

I want a .getChanges(snapShot, liveData) utility function that returns the following:

var differences = {
    name: "Joseph",
    favoriteColorPriority: {
        pink: 3,
        blue: 2
    }
};

I was hoping that the _.underscore library might have something like this, but I couldn't find anything that seemed to work like this.

like image 562
Armchair Bronco Avatar asked Jun 13 '12 19:06

Armchair Bronco


2 Answers

I don't think there is such a function in underscore, but it's easy to implement yourself:

function getChanges(prev, now) {
    var changes = {};
    for (var prop in now) {
        if (!prev || prev[prop] !== now[prop]) {
            if (typeof now[prop] == "object") {
                var c = getChanges(prev[prop], now[prop]);
                if (! _.isEmpty(c) ) // underscore
                    changes[prop] = c;
            } else {
                changes[prop] = now[prop];
            }
        }
    }
    return changes;
}

or

function getChanges(prev, now) {
    var changes = {}, prop, pc;
    for (prop in now) {
        if (!prev || prev[prop] !== now[prop]) {
            if (typeof now[prop] == "object") {
                if(c = getChanges(prev[prop], now[prop]))
                    changes[prop] = c;
            } else {
                changes[prop] = now[prop];
            }
        }
    }
    for (prop in changes)
        return changes;
    return false; // false when unchanged
}

This will not work with Arrays (or any other non-plain-Objects) or differently structured objects (removals, primitive to object type changes).

like image 112
Bergi Avatar answered Oct 02 '22 04:10

Bergi


Posting my own answer so folks can see the final implementation that also works with arrays. In the code below, "um" is my namespace, and I'm also using the _.isArray() and _.isObject methods from Underscore.js.

The code that looks for "_KO" is used to skip past Knockout.js members that are present in the object.

// This function compares 'now' to 'prev' and returns a new JavaScript object that contains only
// differences between 'now' and 'prev'. If 'prev' contains members that are missing from 'now',
// those members are *not* returned. 'now' is treated as the master list.
um.utils.getChanges = function (prev, now) {
    var changes = {};
    var prop = {};
    var c = {};
    //-----

    for (prop in now) { //ignore jslint
        if (prop.indexOf("_KO") > -1) {
            continue; //ignore jslint
        }

        if (!prev || prev[prop] !== now[prop]) {
            if (_.isArray(now[prop])) {
                changes[prop] = now[prop];
            }
            else if (_.isObject(now[prop])) {
                // Recursion alert
                c = um.utils.getChanges(prev[prop], now[prop]);
                if (!_.isEmpty(c)) {
                    changes[prop] = c;
                }
            } else {
                changes[prop] = now[prop];
            }
        }
    }

    return changes;
};
like image 40
Armchair Bronco Avatar answered Oct 02 '22 06:10

Armchair Bronco