Deep pick using lodash/underscore 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 = { a: 5, b: { a: 1}, c: { b: 3}};
pickDeep(testCase2, 'a'); // {a: 5, b: {a: 1}}
var testCase3 = { a: 5, b: [ { a: 1}, { c: 2}, {d: 3} ], c: [ {c: 2}, {d : 3}]}
pickDeep(testCase3, 'a'); //{ a: 5, b: [ {a: 1} ]}
var testCase4 = [ {a: 5}, {b: 2}, {c: {a :3 }}, {d: [ {a: 4}] }, z: [ { f: 34}] ];
pickDeep(testCase4, 'a'); // [ {a:5}, {c: {a:3}}, {d: [ {a:4}]}];


Execution Code

function isIn(collection) {
    return function(value, key) {
        return _.contains(collection, key);
    }
}

console.log(pickDeep(data, isIn(['a', 'c'])));
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 &amp;&amp; _.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