8
\$\begingroup\$

The reason I want to do this is that the JavaScript objects are created from XML elements and if the element has changed I want to be able to detect that change in the JavaScript objects. So I wrote a function which maps out the differences between the first and second object. Can you improve on this function? (mapObjectDifferences, not the helper functions.)

<html>
<head>
<title>MAP OBJECT DIFFERENCES</title>
<script type="text/javascript">

    function isArray(object) {
        return Object.prototype.toString.apply(object) == "[object Array]";
    }

    function getType(variable) {
        var type = typeof variable;
        if (type == "object")
            if (isArray(variable))
                return "array";
        return type;
    }

    function arrayToObject(array) {
        var object = {};
        for (var i = 0; i < array.length; ++i)
            if (array[i] !== "undefined") object[i] = array[i];
        return object;
    }

    function mapObjectDifferences(outerObject, innerObject) {
        var _mapObjectDifferences = function (outerObject, innerObject, parentMap, parentName) {
            var localMap = {};
            for (var outerProp in outerObject) {
                if (outerObject.hasOwnProperty(outerProp)) {
                    var match = false;
                    var outerPropValue = outerObject[outerProp];
                    var outerType = getType(outerPropValue);
                    var result;
                    for (var innerProp in innerObject) {
                        if (innerObject.hasOwnProperty(innerProp)) {
                            var innerPropValue = innerObject[innerProp];
                            var innerType = getType(innerPropValue);
                            if (outerProp == innerProp && outerType == innerType) {
                                match = true;
                                if (outerType == "array" || outerType == "object") {
                                    if (outerType == "array") {
                                        outerPropValue = arrayToObject(outerPropValue);
                                        innerPropValue = arrayToObject(innerPropValue);
                                    }
                                    _mapObjectDifferences(outerPropValue, innerPropValue, localMap, outerProp);
                                }
                                break;
                            }
                        }
                    }
                    if (match == false) {
                        localMap[outerProp] = outerType;
                        if (parentMap)
                            parentMap[parentName] = localMap;
                    }
                    else if (parentMap) {
                        var difChild = false;
                        for (var prop in localMap)
                            if (localMap.hasOwnProperty(prop)) {
                                difChild = true;
                                break;
                            }
                        if (difChild == true)
                            parentMap[parentName] = localMap;
                    }
                }
            }
            return localMap;
        }
        return _mapObjectDifferences(outerObject, innerObject);
    }

    var o1 = {
        val: "level one",
        val2: 1,
        val3: 3,
        m: {
            s2: "this is level two",
            l1: ["a", "b", "c", 1, {a:"a"}],
            ao: [{ x: "1" }, { y: 1 }, { z: 1}]
        },
        n: {
            n1: { abc: 123 }
        }
    };
    var o2 = {
        val: "level on23e",
        val2: 1,

        m: {
            s3: "this is level two",
             l1: ["a", "b", "c"],
            ao: [{ x: 1 }, { y: 1 }, { z: "3"}]
        },
        n: {
            n1: { abc: "abc" }
        }
    }
    var result = mapObjectDifferences(o1, o2);
    debugger;
</script>
</head>
<body>
</body>
</html>
\$\endgroup\$
1
  • \$\begingroup\$ You can simply use the jQuery extends function...(If jQuery is an option) \$\endgroup\$
    – gdoron
    May 2, 2012 at 18:08

4 Answers 4

7
\$\begingroup\$

The existing suggestions are good for trimming down your current code, but, as always, the most important thing to optimize is the algorithm itself. I took a crack at your problem, as I understand it, and here's the result:

function difference(o1, o2) {
    var k, kDiff,
        diff = {};
    for (k in o1) {
        if (!o1.hasOwnProperty(k)) {
        } else if (typeof o1[k] != 'object' || typeof o2[k] != 'object') {
            if (!(k in o2) || o1[k] !== o2[k]) {
                diff[k] = o2[k];
            }
        } else if (kDiff = difference(o1[k], o2[k])) {
            diff[k] = kDiff;
        }
    }
    for (k in o2) {
        if (o2.hasOwnProperty(k) && !(k in o1)) {
            diff[k] = o2[k];
        }
    }
    for (k in diff) {
        if (diff.hasOwnProperty(k)) {
            return diff;
        }
    }
    return false;
}

Note that I didn't try to mimic your code exactly:

  • Arrays are compared thoroughly, not just on numbered properties.
  • Difference map contains new values themselves, not their types.
  • If there are no differences then the function returns false, not {}.
\$\endgroup\$
2
\$\begingroup\$

I was going to post this as a comment to palacsint's answer but it helps to have code blocks.

Because you're using this pattern in several places:

for (var prop in obj) {
  if(obj.hasOwnProperty(prop)) {
    ...
  }
}

One way you can reduce the depth of the "arrowing" is to extract that out into its own function:

function iterateMap(obj, fn, scope) {
  for(prop in obj) {
    if(obj.hasOwnProperty(prop)) {
      fn.call(scope || this, prop);
    }
  }
}

...

iterateMap(obj, function(prop) {
  ...
});
\$\endgroup\$
2
  • \$\begingroup\$ @palacsint: Oops, sorry! I've always wondered, what does your username mean? \$\endgroup\$
    – seand
    Jan 9, 2013 at 21:11
  • 1
    \$\begingroup\$ Np :) It comes from 'palacsinta' (which means 'pancake' in Hungarian) without the last letter, since IRCnet allows (or allowed a couple of years ago) only nine characters in the nickname. \$\endgroup\$
    – palacsint
    Jan 9, 2013 at 22:19
1
\$\begingroup\$

Replacing conditions with guard clauses would improve readability a little bit. (Flattening Arrow Code)

I would try to build two maps first - one for properties of the first object, and one for the other - then compare the two maps. The key of the map could be the name of the property (in the parentProperty.childProperty format if a property is an other object). The value could be the type of the property.

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

There's a library that might help you: https://github.com/flitbit/diff

Given an origin object, and another object, it'll spit out an array of differences between one and the other. Example from their docs:

var lhs = {
    name: 'my object',
    description: 'it\'s an object!',
    details: {
        it: 'has',
        an: 'array',
        with: ['a', 'few', 'elements']
    }
};

var rhs = {
    name: 'updated object',
    description: 'it\'s an object!',
    details: {
        it: 'has',
        an: 'array',
        with: ['a', 'few', 'more', 'elements', { than: 'before' }]
    }
};

var differences = diff(lhs, rhs);

Results in differences being:

[ { kind: 'E',
    path: [ 'name' ],
    lhs: 'my object',
    rhs: 'updated object' },
  { kind: 'E',
    path: [ 'details', 'with', 2 ],
        lhs: 'elements',
        rhs: 'more' },
  { kind: 'A',
    path: [ 'details', 'with' ],
    index: 3,
    item: { kind: 'N', rhs: 'elements' } },
  { kind: 'A',
    path: [ 'details', 'with' ],
    index: 4,
    item: { kind: 'N', rhs: { than: 'before' } } } ]
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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