Skip to content

Instantly share code, notes, and snippets.

@furf
Created July 30, 2012 17:06
Show Gist options
  • Save furf/3208381 to your computer and use it in GitHub Desktop.
Save furf/3208381 to your computer and use it in GitHub Desktop.
underscore.js mixin for plucking nested properties
_.mixin({
// Get/set the value of a nested property
deep: function (obj, key, value) {
var keys = key.replace(/\[(["']?)([^\1]+?)\1?\]/g, '.$2').replace(/^\./, '').split('.'),
root,
i = 0,
n = keys.length;
// Set deep value
if (arguments.length > 2) {
root = obj;
n--;
while (i < n) {
key = keys[i++];
obj = obj[key] = _.isObject(obj[key]) ? obj[key] : {};
}
obj[keys[i]] = value;
value = root;
// Get deep value
} else {
while ((obj = obj[keys[i++]]) != null && i < n) {};
value = i < n ? void 0 : obj;
}
return value;
}
});
// Usage:
//
// var obj = {
// a: {
// b: {
// c: {
// d: ['e', 'f', 'g']
// }
// }
// }
// };
//
// Get deep value
// _.deep(obj, 'a.b.c.d[2]'); // 'g'
//
// Set deep value
// _.deep(obj, 'a.b.c.d[2]', 'george');
//
// _.deep(obj, 'a.b.c.d[2]'); // 'george'
_.mixin({
pluckDeep: function (obj, key) {
return _.map(obj, function (value) { return _.deep(value, key); });
}
});
// Usage:
//
// var arr = [{
// deeply: {
// nested: 'foo'
// }
// }, {
// deeply: {
// nested: 'bar'
// }
// }];
//
// _.pluckDeep(arr, 'deeply.nested'); // ['foo', 'bar']
_.mixin({
// Return a copy of an object containing all but the blacklisted properties.
unpick: function (obj) {
obj || (obj = {});
return _.pick(obj, _.difference(_.keys(obj), _.flatten(Array.prototype.slice.call(arguments, 1))));
}
});
@ross-nordstrom
Copy link

Worth noting, you can use native Underscore (>= v1.8.0) to achieve this by using _.property along with _.compose. Example below:

var nested = {
   colors: {
     red: '#ff0000',
     green: '#008000',
     blue: '#0000FF'
   }
}

var getGreen = _.compose(_.property('green'), _.property('colors'));
console.log({normal: nested.colors.green, underscore: getGreen(nested)})
//==> Object {normal: "#008000", underscore: "#008000"}

Definitely not the most palatable way to do it...

You could put this concept into a mixin easily though:

_.mixin({
  /* usage: _.nestedProperty('colors', 'green') */
  nestedProperty: function(/*args*/) {
    var props = _.toArray(arguments);
    // Reverse b/c compose operates right to left, but usage is left to right
    // Map result is [_.property(argN), .... _.property(arg2), _.property(arg1)]
    return _.compose.apply(null, props.reverse().map(_.property) );
});

@patrickml
Copy link

It seems to have issues when you go too deep like

//Define X
let x = { test1 : { test2: { test3: { test4 : 'Hello World' } } } };
_.deep(x, 'test1.test2.test3.test4') // This will work but now lets make it to where test 3 doesn't exist

x = { test1 : { test2: {  } };
_.deep(x, 'test1.test2.test3.test4') // This will throw an error on lookup which i can understand since this also has the ability to set but i thought i would bring this up

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