45

I want to update an object that could look like this:

currentObject = {
    someValue : "value",
    myObject : {
        attribute1 : "foo",
        attribute2 : "bar"
    }
};

.. with an object that contains some changes i.e.:

updateObject = {
    myObject : {
        attribute2 : "hello world"
    }
};

At the end I would like to have currentObject updated so that:

currentObject.myObject.attribute2 == "hello world"

That should be posible for other objects as well.. As a solution I thought about iterating over the object and somehow take care of the namespace. But I wonder if there is an easy solution for that problem by using a library like jQuery or prototype.

cнŝdk
  • 31,391
  • 7
  • 56
  • 78
Luca Hofmann
  • 1,442
  • 2
  • 14
  • 26
  • If you use jQuery, there is [`$.extend`](http://api.jquery.com/jQuery.extend/) that should do what you want. – gen_Eric Sep 21 '12 at 16:17
  • @RocketHazmat: No, it is not recursive. – Bergi Sep 21 '12 at 16:19
  • 1
    @Bergi: If you pass `true` as the 1st parameter, it is! ;-) – gen_Eric Sep 21 '12 at 16:21
  • @Bergi Please look something up before you claim something about it. Here's a link so you can read about `.extend`: http://api.jquery.com/jQuery.extend/ – Ian Sep 21 '12 at 16:27
  • @RocketHazmat: Right, I always forget that (I don't like the function much because of its array handling) – Bergi Sep 21 '12 at 16:29

4 Answers4

27

I suggest using underscore.js (or better, lo-dash) extend:

_.extend(destination, *sources)

Copy all of the properties in the source objects over to the destination object, and return the destination object. It's in-order, so the last source will override properties of the same name in previous arguments.

_.extend({name: 'moe'}, {age: 50});
=> {name: 'moe', age: 50}
bluehallu
  • 10,205
  • 9
  • 44
  • 61
  • 7
    As far as I can see, both Underscore's and Lodash's `extend` will overwrite the `myObject` property, instead of simply updating it (i.e. it will contain _only_ `attribute2`). Lodash's `merge` would work, however. – TataBlack Jul 07 '15 at 17:12
  • 1
    As pointed out by @TataBlack, given `var a = {name: 'moe', age: 50}, b = {age: 30}; var c = _.merge({}, a, b);`, `c` will be `{name: 'moe', age: 30}` while `a` and `b` stay unchanged. – Season Oct 11 '15 at 09:10
14
function update(obj/*, …*/) {
    for (var i=1; i<arguments.length; i++) {
        for (var prop in arguments[i]) {
            var val = arguments[i][prop];
            if (typeof val == "object") // this also applies to arrays or null!
                update(obj[prop], val);
            else
                obj[prop] = val;
        }
    }
    return obj;
}

should do the trick: update(currentObject, updateObject). You might want to add some type checks, like Object(obj) === obj to extend only real objects with real objects, use a correct loop for arrays or hasOwnProperty tests.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
4

Here's an Object.keys and recursive example:

// execute object update function
update(currentObject, updateObject)

// instantiate object update function
function update (targetObject, obj) {
  Object.keys(obj).forEach(function (key) {

    // delete property if set to undefined or null
    if ( undefined === obj[key] || null === obj[key] ) {
      delete targetObject[key]
    }

    // property value is object, so recurse
    else if ( 
        'object' === typeof obj[key] 
        && !Array.isArray(obj[key]) 
    ) {

      // target property not object, overwrite with empty object
      if ( 
        !('object' === typeof targetObject[key] 
        && !Array.isArray(targetObject[key])) 
      ) {
        targetObject[key] = {}
      }

      // recurse
      update(targetObject[key], obj[key])
    }

    // set target property to update property
    else {
      targetObject[key] = obj[key]
    }
  })
}

JSFiddle demo (open console).

bloodyKnuckles
  • 11,551
  • 3
  • 29
  • 37
1

A simple implementation would look like this.

function copyInto(target /*, source1, sourcen */) {
    if (!target || typeof target !== "object")
        target = {};

    if (arguments.length < 2)
        return target;

    for (var len = arguments.length - 1; len > 0; len--)
        cloneObject(arguments[len-1], arguments[len]);

    return target;
}

function cloneObject(target, source) {
    if (!source || !target || typeof source !== "object" || typeof target !== "object")
        throw new TypeError("Invalid argument");

    for (var p in source)
        if (source.hasOwnProperty(p))
            if (source[p] && typeof source[p] === "object")
                if (target[p] && typeof target[p] === "object")
                    cloneObject(target[p], source[p]);
                else
                    target[p] = source[p];
            else 
                target[p] = source[p];
}

This assumes no inherited properties should be cloned. It also does no checks for things like DOM objects, or boxed primitives.

We need to iterate in reverse through the arguments so that the copy is done in a right to left matter.

Then we make a separate cloneObject function to handle the recursive copying of nested objects in a manner that doesn't interfere with the right to left copying of the original object arguments.

It also ensures that the initial target is a plain object.

The cloneObject function will throw an error if a non-object was passed to it.

  • Why does this copy from source to source to … to target? – Bergi Sep 21 '12 at 16:51
  • @Bergi: Just a different approach I guess, but I do think that I should make it non-destructive by creating interim objects instead of modifying the originals. Also, I should really differentiate between Arrays and Objects in case there's a `{foo:[...]} <- {foo:{...}}` situation. –  Sep 21 '12 at 17:11
  • Yes, it should be nondestructive for the sources - but you don't need interim objects, just `cloneInto(target)`. And yes, extending plain objects with arrays is a pain :-) – Bergi Sep 22 '12 at 09:26