49

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.

5
  • If you use jQuery, there is $.extend that should do what you want. Commented Sep 21, 2012 at 16:17
  • @RocketHazmat: No, it is not recursive. Commented Sep 21, 2012 at 16:19
  • 1
    @Bergi: If you pass true as the 1st parameter, it is! ;-) Commented Sep 21, 2012 at 16:21
  • @Bergi Please look something up before you claim something about it. Here's a link so you can read about .extend: api.jquery.com/jQuery.extend Commented Sep 21, 2012 at 16:27
  • @RocketHazmat: Right, I always forget that (I don't like the function much because of its array handling) Commented Sep 21, 2012 at 16:29

4 Answers 4

32

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}
Sign up to request clarification or add additional context in comments.

2 Comments

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.
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.
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.

Comments

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).

Comments

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.

3 Comments

Why does this copy from source to source to … to target?
@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.
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 :-)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.