Skip to content

Instantly share code, notes, and snippets.

@mikermcneil
Created October 10, 2012 03:06
Show Gist options
  • Save mikermcneil/3862932 to your computer and use it in GitHub Desktop.
Save mikermcneil/3862932 to your computer and use it in GitHub Desktop.
Recursive versions of underscore js methods
// Recursive underscore methods
_.recursive = {
// fn(value,keyChain)
all: function(collection,fn,maxDepth) {
if (!_.isObject(collection)) {
return true;
}
// Default value for maxDepth
maxDepth = maxDepth || 50;
// Kick off recursive function
return _all(collection,null,[],fn,0);
function _all(item,key,keyChain,fn,depth) {
var lengthenedKeyChain = [];
if (depth > maxDepth) {
throw new Error ('Depth of object being parsed exceeds maxDepth (). Maybe it links to itself?');
}
// If the key is null, this is the root item, so skip this step
// Descend
if (key !== null && keyChain) {
lengthenedKeyChain = keyChain.slice(0);
lengthenedKeyChain.push(key);
}
// If the current item is a collection
if (_.isObject(item)) {
return _.all(item,function(subval,subkey) {
return _all(subval,subkey,lengthenedKeyChain,fn,depth+1);
});
}
// Leaf items land here and execute the iterator
else {
return fn(item,lengthenedKeyChain);
}
}
},
// fn(original,newOne,anotherNewOne,...)
extend: function(original, newObj) {
// TODO: make this work for more than one newObj
// var newObjects = _.toArray(arguments).shift();
return _.extend(original, objMap(newObj, function(newVal, key) {
var oldVal = original[key];
// If the new value is a non-object or array,
// or the old value is a non-object or array, use it
if(_.isArray(newVal) || !_.isObject(newVal) || _.isArray(oldVal) || !_.isObject(oldVal)) {
return newVal || oldVal;
}
// Otherwise, we have to descend recursively
else {
return _.recursive.extend(oldVal, newVal);
}
}));
},
// fn(original,newOne,anotherNewOne,...)
defaults: function(original, newObj) {
// TODO: make this work for more than one newObj
// var newObjects = _.toArray(arguments).shift();
return _.extend(original, objMap(newObj, function(newVal, key) {
var oldVal = original[key];
// If the new value is a non-object or array,
// or the old value is a non-object or array, use it
if(_.isArray(newVal) || !_.isObject(newVal) || _.isArray(oldVal) || !_.isObject(oldVal)) {
return oldVal || newVal;
}
// Otherwise, we have to descend recursively
else {
return _.recursive.extend(oldVal, newVal);
}
}));
}
};
// ### _.objMap
// _.map for objects, keeps key/value associations
function objMap(input, mapper, context) {
return _.reduce(input, function(obj, v, k) {
obj[k] = mapper.call(context, v, k, input);
return obj;
}, {}, context);
}
@jdalton
Copy link

jdalton commented Nov 14, 2012

You might check out how Lo-Dash does its recursive deep _.clone and _.merge. They also work with objects containing circular references.

@mikermcneil
Copy link
Author

@jdalton Thanks! This is totally just a hackjob. We're using lodash in the new development branch of Sails.

@astral303
Copy link

FYI, recursive defaults is now supported using, as per @jdalton:

A deep _.defaults can now be implemented by _.merge(object, source, _.defaults)

from lodash/lodash#154 ...

This is for anyone else who arrives here via a search engine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment