From fd526e8754b47fd98e137eb25ce56d35929ddb43 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sun, 12 Jul 2015 15:40:48 -0700 Subject: [PATCH] Split `_.omit` and `_.pick` into `_.omitBy` and `_.pickBy`. --- lodash.src.js | 171 +++++++++++++++++++++---------------- test/test.js | 227 +++++++++++++++++++++++++++----------------------- 2 files changed, 220 insertions(+), 178 deletions(-) diff --git a/lodash.src.js b/lodash.src.js index 5769531f9..fa1c4984e 100644 --- a/lodash.src.js +++ b/lodash.src.js @@ -2400,6 +2400,48 @@ } } + /** + * The base implementation of `_.pick` without support for individual property names. + * + * @private + * @param {Object} object The source object. + * @param {string[]} props The property names to pick. + * @returns {Object} Returns the new object. + */ + function basePick(object, props) { + object = toObject(object); + + var index = -1, + length = props.length, + result = {}; + + while (++index < length) { + var key = props[index]; + if (key in object) { + result[key] = object[key]; + } + } + return result; + } + + /** + * The base implementation of `_.pickBy` without support for callback shorthands. + * + * @private + * @param {Object} object The source object. + * @param {Function} predicate The function invoked per iteration. + * @returns {Object} Returns the new object. + */ + function basePickBy(object, predicate) { + var result = {}; + baseForIn(object, function(value, key, object) { + if (predicate(value, key, object)) { + result[key] = value; + } + }); + return result; + } + /** * The base implementation of `_.property` without support for deep paths. * @@ -4029,50 +4071,6 @@ : objectValue; } - /** - * A specialized version of `_.pick` which picks `object` properties - * specified by `props`. - * - * @private - * @param {Object} object The source object. - * @param {string[]} props The property names to pick. - * @returns {Object} Returns the new object. - */ - function pickByArray(object, props) { - object = toObject(object); - - var index = -1, - length = props.length, - result = {}; - - while (++index < length) { - var key = props[index]; - if (key in object) { - result[key] = object[key]; - } - } - return result; - } - - /** - * A specialized version of `_.pick` which picks `object` properties - * `predicate` returns truthy for. - * - * @private - * @param {Object} object The source object. - * @param {Function} predicate The function invoked per iteration. - * @returns {Object} Returns the new object. - */ - function pickByPredicate(object, predicate) { - var result = {}; - baseForIn(object, function(value, key, object) { - if (predicate(value, key, object)) { - result[key] = value; - } - }); - return result; - } - /** * Reorder `array` according to the specified indexes where the element at * the first index is assigned as the first element, the element at @@ -9275,9 +9273,8 @@ * @memberOf _ * @category Object * @param {Object} object The source object. - * @param {Function|...(string|string[])} [predicate] The function invoked per - * iteration or property names to omit, specified as individual property - * names or arrays of property names. + * @param {string|string[]} [props] The property names to omit, specified as + * individual property names or arrays of property names. * @returns {Object} Returns the new object. * @example * @@ -9285,23 +9282,39 @@ * * _.omit(object, 'age'); * // => { 'user': 'fred' } - * - * _.omit(object, _.isNumber); - * // => { 'user': 'fred' } */ var omit = restParam(function(object, props) { if (object == null) { return {}; } - if (typeof props[0] != 'function') { - var props = arrayMap(baseFlatten(props), String); - return pickByArray(object, baseDifference(keysIn(object), props)); - } - var predicate = props[0]; - return pickByPredicate(object, function(value, key, object) { + var props = arrayMap(baseFlatten(props), String); + return basePick(object, baseDifference(keysIn(object), props)); + }); + + /** + * The opposite of `_.pickBy`; this method creates an object composed of the + * own and inherited enumerable properties of `object` that `predicate` does + * not return truthy for. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'user': 'fred', 'age': 40 }; + * + * _.omitBy(object, _.isNumber); + * // => { 'user': 'fred' } + */ + function omitBy(object, predicate) { + predicate = getIteratee(predicate); + return basePickBy(object, function(value, key, object) { return !predicate(value, key, object); }); - }); + } /** * Creates a two dimensional array of the key-value pairs for `object`, @@ -9333,19 +9346,14 @@ } /** - * Creates an object composed of the picked `object` properties. Property - * names may be specified as individual arguments or as arrays of property - * names. If `predicate` is provided it's invoked for each property of `object` - * picking the properties `predicate` returns truthy for. The predicate is - * invoked with three arguments: (value, key, object). + * Creates an object composed of the picked `object` properties. * * @static * @memberOf _ * @category Object * @param {Object} object The source object. - * @param {Function|...(string|string[])} [predicate] The function invoked per - * iteration or property names to pick, specified as individual property - * names or arrays of property names. + * @param {string|string[]} [props] The property names to pick, specified as + * individual property names or arrays of property names. * @returns {Object} Returns the new object. * @example * @@ -9353,19 +9361,32 @@ * * _.pick(object, 'user'); * // => { 'user': 'fred' } - * - * _.pick(object, _.isString); - * // => { 'user': 'fred' } */ var pick = restParam(function(object, props) { - if (object == null) { - return {}; - } - return typeof props[0] == 'function' - ? pickByPredicate(object, props[0]) - : pickByArray(object, baseFlatten(props)); + return object == null ? {} : basePick(object, baseFlatten(props)); }); + /** + * Creates an object composed of the `object` properties `predicate` returns + * truthy for. The predicate is invoked with three arguments: (value, key, object). + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'user': 'fred', 'age': 40 }; + * + * _.pickBy(object, _.isString); + * // => { 'user': 'fred' } + */ + function pickBy(object, predicate) { + return object == null ? {} : basePickBy(object, getIteratee(predicate)); + } + /** * This method is like `_.get` except that if the resolved value is a function * it's invoked with the `this` binding of its parent object and its result @@ -11367,12 +11388,14 @@ lodash.modArgs = modArgs; lodash.negate = negate; lodash.omit = omit; + lodash.omitBy = omitBy; lodash.once = once; lodash.pairs = pairs; lodash.partial = partial; lodash.partialRight = partialRight; lodash.partition = partition; lodash.pick = pick; + lodash.pickBy = pickBy; lodash.property = property; lodash.propertyOf = propertyOf; lodash.pull = pull; diff --git a/test/test.js b/test/test.js index 93d9caf28..05645729f 100644 --- a/test/test.js +++ b/test/test.js @@ -4642,9 +4642,9 @@ 'mapValues', 'maxBy', 'minBy', - 'omit', + 'omitBy', 'partition', - 'pick', + 'pickBy', 'reject', 'some' ]; @@ -4678,8 +4678,8 @@ var forInMethods = [ 'forIn', 'forInRight', - 'omit', - 'pick' + 'omitBy', + 'pickBy' ]; var iterationMethods = [ @@ -4701,8 +4701,8 @@ 'forOwnRight', 'mapKeys', 'mapValues', - 'omit', - 'pick' + 'omitBy', + 'pickBy' ]; var rightMethods = [ @@ -4742,7 +4742,7 @@ isFind = /^find/.test(methodName), isSome = methodName == 'some'; - test('`_.' + methodName + '` should provide the correct `iteratee` arguments', 1, function() { + test('`_.' + methodName + '` should provide the correct iteratee arguments', 1, function() { if (func) { var args, expected = [1, 0, array]; @@ -11211,41 +11211,13 @@ (function() { var args = arguments, - object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, - expected = { 'b': 2, 'd': 4 }; + object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }; - test('should create an object with omitted properties', 2, function() { - deepEqual(_.omit(object, 'a'), { 'b': 2, 'c': 3, 'd': 4 }); - deepEqual(_.omit(object, 'a', 'c'), expected); - }); - - test('should flatten `props`', 1, function() { + test('should flatten `props`', 2, function() { + deepEqual(_.omit(object, 'a', 'c'), { 'b': 2, 'd': 4 }); deepEqual(_.omit(object, ['a', 'd'], 'c'), { 'b': 2 }); }); - test('should iterate over inherited properties', 1, function() { - function Foo() {} - Foo.prototype = object; - - deepEqual(_.omit(new Foo, 'a', 'c'), expected); - }); - - test('should return an empty object when `object` is nullish', 2, function() { - objectProto.a = 1; - _.each([null, undefined], function(value) { - deepEqual(_.omit(value, 'valueOf'), {}); - }); - delete objectProto.a; - }); - - test('should work with `arguments` objects as secondary arguments', 1, function() { - deepEqual(_.omit(object, args), expected); - }); - - test('should work with an array `object` argument', 1, function() { - deepEqual(_.omit([1, 2, 3], '0', '2'), { '1': 2 }); - }); - test('should work with a primitive `object` argument', 1, function() { stringProto.a = 1; stringProto.b = 2; @@ -11256,28 +11228,16 @@ delete stringProto.b; }); - test('should work with a predicate argument', 1, function() { - var actual = _.omit(object, function(num) { - return num != 2 && num != 4; + test('should return an empty object when `object` is nullish', 2, function() { + objectProto.a = 1; + _.each([null, undefined], function(value) { + deepEqual(_.omit(value, 'valueOf'), {}); }); - - deepEqual(actual, expected); + delete objectProto.a; }); - test('should provide the correct predicate arguments', 1, function() { - var args, - object = { 'a': 1, 'b': 2 }, - lastKey = _.keys(object).pop(); - - var expected = lastKey == 'b' - ? [1, 'a', object] - : [2, 'b', object]; - - _.omit(object, function() { - args || (args = slice.call(arguments)); - }); - - deepEqual(args, expected); + test('should work with `arguments` objects as secondary arguments', 1, function() { + deepEqual(_.omit(object, args), { 'b': 2, 'd': 4 }); }); test('should coerce property names to strings', 1, function() { @@ -11287,6 +11247,55 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.omitBy'); + + (function() { + test('should work with a predicate argument', 1, function() { + var object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }; + + var actual = _.omitBy(object, function(num) { + return num != 2 && num != 4; + }); + + deepEqual(actual, { 'b': 2, 'd': 4 }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('omit methods'); + + _.each(['omit', 'omitBy'], function(methodName) { + var expected = { 'b': 2, 'd': 4 }, + func = _[methodName], + object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }; + + var prop = methodName == 'omit' ? _.identity : function(props) { + props = typeof props == 'string' ? [props] : props; + return function(value, key) { + return _.includes(props, key); + }; + }; + + test('`_.' + methodName + '` should create an object with omitted properties', 2, function() { + deepEqual(func(object, prop('a')), { 'b': 2, 'c': 3, 'd': 4 }); + deepEqual(func(object, prop(['a', 'c'])), expected); + }); + + test('`_.' + methodName + '` should iterate over inherited properties', 1, function() { + function Foo() {} + Foo.prototype = object; + + deepEqual(func(new Foo, prop(['a', 'c'])), expected); + }); + + test('`_.' + methodName + '` should work with an array `object` argument', 1, function() { + deepEqual(func([1, 2, 3], prop(['0', '2'])), { '1': 2 }); + }); + }); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.once'); (function() { @@ -11926,25 +11935,18 @@ (function() { var args = arguments, - object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, - expected = { 'a': 1, 'c': 3 }; + object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }; - test('should create an object of picked properties', 2, function() { - deepEqual(_.pick(object, 'a'), { 'a': 1 }); - deepEqual(_.pick(object, 'a', 'c'), expected); - }); - - test('should flatten `props`', 1, function() { + test('should flatten `props`', 2, function() { + deepEqual(_.pick(object, 'a', 'c'), { 'a': 1, 'c': 3 }); deepEqual(_.pick(object, ['a', 'd'], 'c'), { 'a': 1, 'c': 3, 'd': 4 }); }); - test('should iterate over inherited properties', 1, function() { - function Foo() {} - Foo.prototype = object; - - deepEqual(_.pick(new Foo, 'a', 'c'), expected); + test('should work with a primitive `object` argument', 1, function() { + deepEqual(_.pick('', 'slice'), { 'slice': ''.slice }); }); + test('should return an empty object when `object` is nullish', 2, function() { _.each([null, undefined], function(value) { deepEqual(_.pick(value, 'valueOf'), {}); @@ -11952,39 +11954,7 @@ }); test('should work with `arguments` objects as secondary arguments', 1, function() { - deepEqual(_.pick(object, args), expected); - }); - - test('should work with an array `object` argument', 1, function() { - deepEqual(_.pick([1, 2, 3], '1'), { '1': 2 }); - }); - - test('should work with a primitive `object` argument', 1, function() { - deepEqual(_.pick('', 'slice'), { 'slice': ''.slice }); - }); - - test('should work with a predicate argument', 1, function() { - var actual = _.pick(object, function(num) { - return num == 1 || num == 3; - }); - - deepEqual(actual, expected); - }); - - test('should provide the correct predicate arguments', 1, function() { - var args, - object = { 'a': 1, 'b': 2 }, - lastKey = _.keys(object).pop(); - - var expected = lastKey == 'b' - ? [1, 'a', object] - : [2, 'b', object]; - - _.pick(object, function() { - args || (args = slice.call(arguments)); - }); - - deepEqual(args, expected); + deepEqual(_.pick(object, args), { 'a': 1, 'c': 3 }); }); test('should coerce property names to strings', 1, function() { @@ -11994,6 +11964,55 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.pickBy'); + + (function() { + test('should work with a predicate argument', 1, function() { + var object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }; + + var actual = _.pickBy(object, function(num) { + return num == 1 || num == 3; + }); + + deepEqual(actual, { 'a': 1, 'c': 3 }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('pick methods'); + + _.each(['pick', 'pickBy'], function(methodName) { + var expected = { 'a': 1, 'c': 3 }, + func = _[methodName], + object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }; + + var prop = methodName == 'pick' ? _.identity : function(props) { + props = typeof props == 'string' ? [props] : props; + return function(value, key) { + return _.includes(props, key); + }; + }; + + test('`_.' + methodName + '` should create an object of picked properties', 2, function() { + deepEqual(func(object, prop('a')), { 'a': 1 }); + deepEqual(func(object, prop(['a', 'c'])), expected); + }); + + test('`_.' + methodName + '` should iterate over inherited properties', 1, function() { + function Foo() {} + Foo.prototype = object; + + deepEqual(func(new Foo, prop(['a', 'c'])), expected); + }); + + test('`_.' + methodName + '` should work with an array `object` argument', 1, function() { + deepEqual(func([1, 2, 3], prop('1')), { '1': 2 }); + }); + }); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.property'); (function() { @@ -17434,7 +17453,7 @@ var acceptFalsey = _.difference(allMethods, rejectFalsey); - test('should accept falsey arguments', 212, function() { + test('should accept falsey arguments', 214, function() { var emptyArrays = _.map(falsey, _.constant([])); _.each(acceptFalsey, function(methodName) {