Reputation: 8315
I have the following collection:
var columns = [
{ key:'url', width:20, type:'text' },
{ key:'title', width:21, type:'text' },
{ key:'desc', width:22, type:'text' },
{ key:'domain', width:23, type:'text' },
{ key:'user', width:24, type:'text' }
];
I'm looking for a method to map an array of objects with picked keys, something along the lines of:
_.mapPick(columns, [width])
// [{width:20},{width:21},{width:22},{width:23},{width:24}]
I know I can extend lo-dash like this:
_.mixin({
mapPick: mapPick:function (objs,keys){
return _.map(objs, function (obj) {
return _.pick(obj,keys)
})
}
});
I'm not sure if there is some native function that I'm missing.
I found a similar question here but I'm looking for a more lo-dash native way.
Upvotes: 49
Views: 41700
Reputation: 11
you can use Curry
var columns = [
{ key:'url', width:20, type:'text' },
{ key:'title', width:21, type:'text' },
{ key:'desc', width:22, type:'text' },
{ key:'domain', width:23, type:'text' },
{ key:'user', width:24, type:'text' }
];
result = _.map(columns, _.curry(_.pick, 2)(_, ['width']));
out:
[
{ width: 20 },
{ width: 21 },
{ width: 22 },
{ width: 23 },
{ width: 24 }
]
Upvotes: 1
Reputation: 103
Why not just using a pick here (~50kb import)?
npm i lodash.pick
Solution:
const pick = require('lodash.pick')
const filterKeys = ['width']
const columns = [
{ key: 'url'. , width: 20, type: 'text' },
{ key: 'title' , width: 21, type: 'text' },
{ key: 'desc' , width: 22, type: 'text' },
{ key: 'domain', width: 23, type: 'text' },
{ key: 'user' , width: 24, type: 'text' },
]
const result = columns.map(item => pick(item, filterKeys))
Output:
[
{ width: 20 },
{ width: 21 },
{ width: 22 },
{ width: 23 },
{ width: 24 }
]
Upvotes: 1
Reputation: 149
I looked extensively into the docs, and it would seem that currently in Lodash 4.17.15 (as of now 26th Jan 2021) no such functionality is directly implemented in the library. Accordingly:
_.map(columns, row => _.pick(row, 'key'))
//Output, when using the columns collection I introduce below:
//[{key: "url"},
// {key: "user"},
// {}]
shall be considered the preferred solution. If the key is not found:
I believe it may be worth noting what advantages and disadvantages may lay in the alternative approaches. Let's have a tour between them, considering the following collection, designed to show some drawbacks that may arise from each solution:
var columns = [
{ 0: 'pos_url', key:'url', width:20, type:'text' },
{ 1: 'pos_user', key:'user', width:24, type:'text' },
"key"
];
It may be a little messy, but I actually wrote a small codepen were you can make your hands dirty and try all it's explained here, feel free to try it:
https://codepen.io/jhack_jos/pen/gOwVReB
You have to open your browser console to see the console.log outputs.
If like me, you actually do not want to use mixins and mess with Lodash, you could also make use of the following function I created, called project
. It is inspired by Michael Fogus "Functional JavaScript", as he introduces a similar function in its talk about "Table-Like" Data and "Applicative Programming". The name comes from the unary operation called projection in relational algebra, which does something very similar.
with ES6
function project(...properties)
{
return function project(object, index, array)
{
return _.pick(object, ...properties);
};
}
without ES6
function project(/* properties here */)
{
var properties = arguments;
return function project(object, index, array)
{
return _.pick.bind(null, object).apply(null, properties);
};
}
Some examples of usage:
_.map(columns, project("key"));
//Output:
//[{key: "url"},
// {key: "user"},
// {}]
_.map(columns, project("key", "width"));
//Output:
//[{key: "url", width: 20},
// {key: "user", width: 24},
// {}]
_.map(columns, project("key", ["width", "type"]));
//Output:
//[{key: "url", width: 20, type: "text"},
// {key: "user", width: 24, type: "text"},
// {}]
How fast are the different implementations of project
?
project
: https://jsbench.me/2gkkfl9pw4If the key is not found:
I actually really like the mixin approach used by @pery mimon in the question. It is also possible to use _.chain
with it, which makes the code very readable. Just keep in mind the possibility of clashing with future Lodash updates. Unfortunately, the algorithm as it is does not behave as one may expect when it is thrown a list of properties as arguments (it ignores all but the first argument). I may suggest the following improvement:
with ES6
_.mixin({
mapPick: function (objs, ...keys) {
return _.map(objs, function (obj) {
return _.pick(obj, ...keys);
})
}
});
without ES6
_.mixin({
mapPick: function (objs) {
var properties = arguments;
return _.map(objs, function (obj) {
return _.pick.bind(null, obj).apply(null, properties);
})
}
});
Thanks to this we can now do:
_.mapPick(columns, "key", "width")
//Output now:
//[{key: "url", width: 20}
// {key: "user", width: 24}
// {}]
//Output of the original mixin from the question:
//[{key: "url"}
// {key: "user"}
// {}]
If the key is not found:
This approach is not recommended, as it may reveal to be tricky if incorrectly applied in edge case scenarios. Lodash docs states _.partialRight
does the following:
This method is like _.partial except that partially applied arguments are appended to the arguments it receives.
This means that _.partialRight(func, arg1, arg2) (arg3, arg4)
resolves into func(arg3, arg4, arg1, arg2)
.
As such, the following code:
_.map(columns, _.partialRight(_.pick, 'key'));
Resolves into:
_.map(columns, (row, index, columns) => _.pick(row, index, columns, 'key'));
//Output:
//[{0: "pos_url", key: "url"},
// {1: "pos_user", key: "user"},
// {2: "y"}]
If the key is not found:
You should therefore use _.unary
to restrict the arguments passed down to _.pick
to just one. Code is as follows:
_.map(columns, _.unary(_.partialRight(_.pick, 'key')));
This resolves to:
_.map(columns, (row, index, columns) => _.pick(row, 'key'));
//Output:
//[{key: "url"},
// {key: "user"},
// {}]
If the key is not found:
You may expect this approach to cover everything and be vanilla, but you may find it slightly inflexible in edge cases. Let's have a look at it:
columns.map(({key}) => ({key}))
//Output:
//[{key: "url"},
// {key: "user"},
// {key: undefined}]
A few relevant facts:
If the key is not found:
_.keys
will still return the key that was not foundThis one is an extra, and does not comply with the question. As another answer pointed out, in case you do not need the keys, you may just extract the values with the _.map
shortcut (previously known as _.pluck
):
_.map(columns, "key")
//Output:
//["url", "user", undefined]
There are a lot of interesting things to know about this way of doing it, as it implicitly makes use of the _.iteratee function, which enables us to make such a brilliant use of _.get with _.map
.
If the key is not found:
Upvotes: 6
Reputation: 1831
With ES6 destructuring and arrow functions you can do it like this:
columns.map(({width}) => ({width}))
No lodash required.
Upvotes: 10
Reputation: 774
I found simpler solution in map examples:
var users = [
{name:'David',age:35},
{name:'Kate',age:28},
];
_.map(users,'age'); // [35,28]
Upvotes: 19
Reputation: 1188
In my case I also need cast to type, and problem was what TS compiler represents Adam Boduch solution return a boolean, so I find a little simple way to do this:
_.map(columns, item => { return { key: item.key } });
In this case you also can add some new properties.
P.S. Because I cant post a comment I will add explanation about partialRight usage here:
_.map(columns, _.partialRight(_.pick, 'key'));
First if u call _.map(array, func)
func will be called for every array element. So it's equal: _.map(array, item => func(item))
.
Second the result of call partialRight will be a function newFunc, so we can represent Adam's code as:
var newFunc = _.partialRight(_.pick, 'key');
_.map(array, item => newFunc(item));
Third we can represent the newFunc logic as:
function newFunc(item) {
return _.pick(item, 'key')
}
Finally I think most understandable and readable solution for this problem is:
_.map(columns, item => _.pick(item, 'key'))
Upvotes: 26
Reputation: 3232
If you only want one of the keys you can do:
_.map(collection, 'key')
Your example:
var columns = [
{ key:'url', width:20, type:'text' },
{ key:'title', width:21, type:'text' },
{ key:'desc', width:22, type:'text' },
{ key:'domain', width:23, type:'text' },
{ key:'user', width:24, type:'text' }
];
var widths = _.map(columns, 'width');
widths
[20, 21, 22, 23, 24]
Upvotes: 4
Reputation: 11211
I think the map() + pick() approach is your best bet. You could compose the callback instead of creating an inline function however:
_.map(columns, _.partialRight(_.pick, 'key'));
Upvotes: 36