Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Copy all of the properties in the source objects over to the destination object, and return the destination object. This method will recursively copy mutual properties which are also objects.
# Create a deep copy of an object. - CoffeeScript conversion of @cederberg's deepClone implementation https://github.com/documentcloud/underscore/pull/595
deepClone = (obj) ->
if !_.isObject(obj) or _.isFunction(obj) then return obj
if _.isDate obj then return new Date do obj.getTime
if _.isRegExp obj then return new RegExp obj.source, obj.toString().replace(/.*\//, "")
isArr = _.isArray obj or _.isArguments obj
func = (memo, value, key) ->
if isArr then memo.push deepClone value
else memo[key] = deepClone value
return memo;
return _.reduce obj, func, if isArr then [] else {}
# Is a given value a basic Object? i.e.: {} || new Object()
isBasicObject = (object) ->
(object.prototype is {}.prototype or object.prototype is Object.prototype) and _.isObject(object) and not _.isArray(object) and not _.isFunction(object) and not _.isDate(object) and not _.isRegExp(object) and not _.isArguments(object)
# Returns a list of the names of every object in an object — that is to say, the name of every property of the object that is an object.
basicObjects = (object) ->
_.filter _.keys(object), (key) -> isBasicObject object[key]
# Returns a list of the names of every array in an object — that is to say, the name of every property of the object that is an array.
arrays = (object) ->
_.filter(_.keys(object), (key) -> _.isArray object[key])
# Copy and combine all of the properties in the source objects over to the destination object and return the destination object. This method will recursively copy shared properties which are also objects and combine arrays.
deepExtendCouple = (destination, source, maxDepth=20) ->
if maxDepth <= 0
console.warn '_.deepExtend(): Maximum depth of recursion hit.'
return _.extend destination, source
sharedObjectKeys = _.intersection(basicObjects(destination), basicObjects(source))
recurse = (key) ->
source[key] = deepExtendCouple destination[key], source[key], maxDepth-1
recurse sharedObjectKey for sharedObjectKey in sharedObjectKeys
sharedArrayKeys = _.intersection(arrays(destination), arrays(source))
combine = (key) ->
source[key] = _.union destination[key], source[key]
combine sharedArrayKey for sharedArrayKey in sharedArrayKeys
_.extend destination, source
# Copy and combine all of the properties in the supplied objects from right to left and return the combined object. This method will recursively copy shared properties which are also objects and combine arrays.
deepExtend = (objects..., maxDepth) ->
if !_.isNumber maxDepth
objects.push maxDepth
maxDepth = 20
if objects.length <= 1 then return objects[0]
if maxDepth <= 0 then return _.extend.apply this, objects
finalObj = do objects.shift
while objects.length > 0
finalObj = deepExtendCouple(finalObj, deepClone(do objects.shift), maxDepth)
return finalObj
_.mixin
deepClone: deepClone
isBasicObject: isBasicObject
basicObjects: basicObjects
arrays: arrays
deepExtend: deepExtend
foo =
array: [3, 4, 5, 1, 'rawr', 'foo']
name:
first: "Earl"
last: "Jones"
job: "Winning"
friends:
"01234":
name:
first: "Whodat"
bar =
array: [1, 2, 3, 'rawr', 'zoom']
name:
middle: "Samus"
job: "Farmer"
friends:
"01234":
name:
last: "Mook"
friends:
deep:
connection:
going:
all:
the:
combined: "Heyo!"
way:
down:
depth: "Too Deep?"
hear: "Not Too Deep"
blam =
name:
first: "Eddie"
job: "Mugger"
friends:
"01234":
name:
first: "Honorary"
friends:
deep:
connection:
going:
all:
the:
way:
down:
depth: "Way Too Deep!"
see: "What I mean?"
"43210":
name:
first: "Guy"
last: "Rogers"
# Use deepClone when testing because deepExtend will modify the first parameter
console.log(_.deepClone(foo),_.deepClone(bar),_.deepClone(blam))
console.log('default', _.deepExtend(_.deepClone(foo), bar, blam))
console.log('0', _.deepExtend(_.deepClone(foo), bar, blam, 0))
console.log('20', _.deepExtend(_.deepClone(foo), bar, blam, 20))
console.log('mixed-default', _.deepExtend(_.deepClone(blam), foo, bar, 20))
console.log('mixed-0', _.deepExtend(_.deepClone(blam), foo, bar, 0))
console.log('mixed-20', _.deepExtend(_.deepClone(blam), foo, bar, 20))
console.log(foo,bar,blam)
(function() {
var arrays, basicObjects, deepClone, deepExtend, deepExtendCouple, isBasicObject,
__slice = [].slice;
deepClone = function(obj) {
var func, isArr;
if (!_.isObject(obj) || _.isFunction(obj)) {
return obj;
}
if (_.isDate(obj)) {
return new Date(obj.getTime());
}
if (_.isRegExp(obj)) {
return new RegExp(obj.source, obj.toString().replace(/.*\//, ""));
}
isArr = _.isArray(obj || _.isArguments(obj));
func = function(memo, value, key) {
if (isArr) {
memo.push(deepClone(value));
} else {
memo[key] = deepClone(value);
}
return memo;
};
return _.reduce(obj, func, isArr ? [] : {});
};
isBasicObject = function(object) {
return (object.prototype === {}.prototype || object.prototype === Object.prototype) && _.isObject(object) && !_.isArray(object) && !_.isFunction(object) && !_.isDate(object) && !_.isRegExp(object) && !_.isArguments(object);
};
basicObjects = function(object) {
return _.filter(_.keys(object), function(key) {
return isBasicObject(object[key]);
});
};
arrays = function(object) {
return _.filter(_.keys(object), function(key) {
return _.isArray(object[key]);
});
};
deepExtendCouple = function(destination, source, maxDepth) {
var combine, recurse, sharedArrayKey, sharedArrayKeys, sharedObjectKey, sharedObjectKeys, _i, _j, _len, _len1;
if (maxDepth == null) {
maxDepth = 20;
}
if (maxDepth <= 0) {
console.warn('_.deepExtend(): Maximum depth of recursion hit.');
return _.extend(destination, source);
}
sharedObjectKeys = _.intersection(basicObjects(destination), basicObjects(source));
recurse = function(key) {
return source[key] = deepExtendCouple(destination[key], source[key], maxDepth - 1);
};
for (_i = 0, _len = sharedObjectKeys.length; _i < _len; _i++) {
sharedObjectKey = sharedObjectKeys[_i];
recurse(sharedObjectKey);
}
sharedArrayKeys = _.intersection(arrays(destination), arrays(source));
combine = function(key) {
return source[key] = _.union(destination[key], source[key]);
};
for (_j = 0, _len1 = sharedArrayKeys.length; _j < _len1; _j++) {
sharedArrayKey = sharedArrayKeys[_j];
combine(sharedArrayKey);
}
return _.extend(destination, source);
};
deepExtend = function() {
var finalObj, maxDepth, objects, _i;
objects = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), maxDepth = arguments[_i++];
if (!_.isNumber(maxDepth)) {
objects.push(maxDepth);
maxDepth = 20;
}
if (objects.length <= 1) {
return objects[0];
}
if (maxDepth <= 0) {
return _.extend.apply(this, objects);
}
finalObj = objects.shift();
while (objects.length > 0) {
finalObj = deepExtendCouple(finalObj, deepClone(objects.shift()), maxDepth);
}
return finalObj;
};
_.mixin({
deepClone: deepClone,
isBasicObject: isBasicObject,
basicObjects: basicObjects,
arrays: arrays,
deepExtend: deepExtend
});
}).call(this);
var user = { "name": { "first": "Earl" }, "friends": ["0","1","3"], "job": "Mad Scientist", "pets": { "dog": "Ralph" } };
var userUpdate = { "name": { "last": "Duncan" }, "friends": ["6","9"], "job": "Happy Scientist", "pets": { "cat": "Judy" }, "city": "Portlandia" };
_.deepExtend(user, userUpdate);
// results in user equalling:
// { "name": { "first": "Earl", "last": "Duncan" }, "friends": ["0","1","3","6","9"], "job": "Happy Scientist", "pets": { "dog": "Ralph", "cat": "Judy" }, "city": "Portlandia" }
var user = { "name": { "first": "Earl" }, "friends": ["0","1","3"], "job": "Mad Scientist", "pets": { "dog": "Ralph" } };
var userUpdate = { "name": { "last": "Duncan" }, "friends": ["6","9"], "job": "Happy Scientist", "pets": { "cat": "Judy" }, "city": "Portlandia" };
_.extend(user, userUpdate);
// results in user equalling:
// { "name": { "last": "Duncan" }, "friends": ["6","9"], "job": "Happy Scientist", "pets": { "cat": "Judy" }, "city": "Portlandia" }
@dschi

This comment has been minimized.

Copy link

@dschi dschi commented Feb 1, 2013

    if !_.isObject obj or _.isFunction obj then return obj

should be

    if !_.isObject(obj) or _.isFunction(obj) then return obj
@chrisritter

This comment has been minimized.

Copy link

@chrisritter chrisritter commented Feb 3, 2013

What's the license on this?

@senica

This comment has been minimized.

Copy link

@senica senica commented Feb 25, 2013

As dschi mentioned for the coffescript version, the js version should be
if (!_.isObject(obj) || _.isFunction(obj)) {
instead of
if (!_.isObject(obj || _.isFunction(obj))) {

@AndrewEastwood

This comment has been minimized.

Copy link

@AndrewEastwood AndrewEastwood commented Mar 26, 2013

What about this:

var clone = JSON.parse(JSON.stringify(obj));

@ElliotChong

This comment has been minimized.

Copy link
Owner Author

@ElliotChong ElliotChong commented Mar 30, 2013

Great catch @dschi and @senica, thanks!

@ElliotChong

This comment has been minimized.

Copy link
Owner Author

@ElliotChong ElliotChong commented Mar 30, 2013

@AndrewEastwood Nice deepClone replacement! You'll lose Date and RegEx handling, but if you're not using those data types that should work well.

@ElliotChong

This comment has been minimized.

Copy link
Owner Author

@ElliotChong ElliotChong commented Mar 30, 2013

@chrisritter Thanks for asking! License is WTFPL.
http://en.wikipedia.org/wiki/WTFPL

@JarnoLeConte

This comment has been minimized.

Copy link

@JarnoLeConte JarnoLeConte commented Dec 7, 2013

'null' values don't work. I get an error in isBasicObject:

TypeError: Cannot read property 'prototype' of null

The problem can be solved by switching the order of the first two clauses in the logical expression in isBasicObject().

  _.isObject(object) && (object.prototype === {}.prototype || object.prototype === Object.prototype)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment