Updating JavaScript object-attributes from another object

44,366

Solution 1

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.

Solution 2

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}

Solution 3

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

Solution 4

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.

Share:
44,366
Luca Hofmann
Author by

Luca Hofmann

Game developer, computer graphics, Android, Java, JavaScript etc.

Updated on July 09, 2022

Comments

  • Luca Hofmann
    Luca Hofmann almost 2 years

    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.

    • gen_Eric
      gen_Eric almost 12 years
      If you use jQuery, there is $.extend that should do what you want.
    • Bergi
      Bergi almost 12 years
      @RocketHazmat: No, it is not recursive.
    • gen_Eric
      gen_Eric almost 12 years
      @Bergi: If you pass true as the 1st parameter, it is! ;-)
    • Ian
      Ian almost 12 years
      @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
    • Bergi
      Bergi almost 12 years
      @RocketHazmat: Right, I always forget that (I don't like the function much because of its array handling)
  • Bergi
    Bergi almost 12 years
    Why does this copy from source to source to … to target?
  • Admin
    Admin almost 12 years
    @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.
  • Bergi
    Bergi almost 12 years
    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 :-)
  • TataBlack
    TataBlack almost 9 years
    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.
  • Season
    Season over 8 years
    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.