HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptModerate

Deep pick using lodash/underscore

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
underscorelodashdeepusingpick

Problem

Use Case

_.pick creates a shallow clone of an object given a predicate that identifies which keys to keep. pickDeep would perform a deep clone of the object and would "pick" up all nested objects containing the given keys. All containers containing the nested objects would remain and would not be removed.

Requirements

  • Recursively apply a pick to each level in an object.



  • If a property is an object/array, then apply a pick.



  • Keep all object/array only if the descendant properties fulfill the pick.



Improvements

I'd like any constructive criticism to improve this code, but I'm mainly interested in advice pertaining to:

  • Readability



  • Flexibility



  • Performance



  • Implementing this with _.pick and _.cloneDeep



Function

(jsfiddle)

function pickDeep(collection, identity, thisArg) {
    var picked = _.pick(collection, identity, thisArg);
    var collections = _.pick(collection, _.isObject, thisArg);

    _.each(collections, function(item, key, collection) {
        var object;
        if (_.isArray(item)) {
            object = _.reduce(item, function(result, value) {
                var picked = pickDeep(value, identity, thisArg);
                if (!_.isEmpty(picked)) {
                    result.push(picked);
                }
                return result;
            }, []);
        } else {
            object = pickDeep(item, identity, thisArg);
        }

        if (!_.isEmpty(object)) {
            picked[key] = object;
        }

    });

    return picked;
}


Test Data

var data = {
    a: 5,
    b: 6,
    c: 7,
    d: {
        a: 65,
        z: 6,
        d: {
            a: 65,
            k: 5
        }
    },
    e: [
        {a : 5},
        {b : 6},
        {c : 7}
    ],
    f: [
        { 
            b : [  { a: 5, z: 5 } ],
            c : 6
        },
        {
            g: 0   
        }
    ]
};


Test Cases

```
var testCase1 = { a: 5, b: 6};
pickDeep(testCase1, 'a'); // {a : 5}
var testCase2

Solution

Here's the most elegant way I can think of writing this. I use transform to handle both Arrays and Objects as your tests showed you wanted to support them, though as pointed out in comments your code didn't.

These changes also allow you to use more than 2 pick properties (pickDeep(set, 'a', 'b', 'c', ['d', 'e'])) as you can with the normal pick/omit.

For lodash 3 and underscore 1.8? you'll need to change _.createCallback to _.iteratee the rest of the code should remain the same.

function pickDeep(collection, predicate, thisArg) {
    if (_.isFunction(predicate)) {
        predicate = _.createCallback(predicate, thisArg);
    } else {
        var keys = _.flatten(_.rest(arguments));
        predicate = function(val, key) {
            return _.contains(keys, key);
        }
    }

    return _.transform(collection, function(memo, val, key) {
        var include = predicate(val, key);
        if (!include && _.isObject(val)) {
            val = pickDeep(val, predicate);
            include = !_.isEmpty(val);
        }
        if (include) {
            _.isArray(collection) ? memo.push(val) : memo[key] = val;
        }
    });
}


This also fixes a the need to check pickDeep({a: [{a: 1}]}) twice to add it to the picked object

Code Snippets

function pickDeep(collection, predicate, thisArg) {
    if (_.isFunction(predicate)) {
        predicate = _.createCallback(predicate, thisArg);
    } else {
        var keys = _.flatten(_.rest(arguments));
        predicate = function(val, key) {
            return _.contains(keys, key);
        }
    }

    return _.transform(collection, function(memo, val, key) {
        var include = predicate(val, key);
        if (!include && _.isObject(val)) {
            val = pickDeep(val, predicate);
            include = !_.isEmpty(val);
        }
        if (include) {
            _.isArray(collection) ? memo.push(val) : memo[key] = val;
        }
    });
}

Context

StackExchange Code Review Q#57976, answer score: 15

Revisions (0)

No revisions yet.