I recently ran into a problem where I needed the following piece of JavaScript
let { [key]: id, ...rest } = obj
So in this post I want to explain what this is doing and how it works.
Spoiler alert: I ended up not implementing it this way because of its obscurity, but it is interesting nontheless
How I ran into this problem?
Imagine we have the following array
const users = [
{ name: 'Michael', group: 1 },
{ name: 'Lukas', group: 1 },
{ name: 'Travis', group: 2 },
]
and we want to group it by the key group
and turn it into a hashMap which would look like this
{
'1': [
{ name: 'Michael' },
{ name: 'Lukas' },
],
'2': [
{ name: 'Travis' },
]
}
Notice how we remove the group from the user
object.
We can achieve this using
users.reduce((result, user) => {
const { group, ...userData } = user
result[group] = result[group] || []
result[group].push(userData)
return result
}, {})
If you are unfamiliar with reduce
check out my article on array methods.
My end goal was to make this function dynamic, right now the group
key is all hardcoded and not computed. But before we look at that, let's check out const { group, ...userData } = user
since it is exactly the expression I want to talk about, just not dynamic.
Destructuring
We know that each user has the keys group
and name
, so in ES6 we can use a feature called destructuring to get individual values from an object.
For example
const { group } = user
would be the same as writing
const group = user.group
and
const { group, name } = user
would likewise be the same as
const group = user.group
const name = user.name
Rest
Now there is one more complexity in our initial line: const { group, ...userData } = user
.
...userData
is taking all key value pairs except for group
and shallow copies them into a new constant named userData
. In this case the variable userData
would be an object with only the name
property.
Don't confuse the rest parameter with spreading. Spreading would be kind of the opposite.
const location = { country: 'Japan', city: 'Tokyo' }
const newLocation = { ...location, zipcode: 123456 }
This takes the location object and spreads it out, so newLocation
will be a completely new object that has all the properties from location
as well as zipcode
.
When is something rest
and when is something spread
? It all depends on which side the assignment is. If something is on the left side of the assignment it would be rest
, if something is on the right side of the assignment it would be spread
.
You can also use the rest parameter for functions.
class BaseArray extends Array {
constructor(...values) { // rest
super(...values) // spread
}
}
With that out of the way, let's look at the dynamic solution.
function groupBy(array, key) {
return array.reduce((result, item) => {
const { [key]: id, ...rest } = item
result[id] = result[id] || new []
result[id].push(rest);
return result;
}, {})
}
Now what the heck is const { [key]: id, ...rest } = item
?
We already know what ...rest
means, so we can ignore that for now. Before explaining [key]: id
, let's look at a simpler example.
Assigning new variable names
Remember this?
const user = { group: 1 }
const { group } = user
console.log(group) //? 1
What if we wanted to apply the value of group
to a different variable name? We can do it like this
const user = { group: 1 }
const { group: id } = user
console.log(id) //? 1
This takes the value of group
and puts it inside the variable id
.
This is actually really useful because sometimes keys would be invalid as variable names.
const foo = { 'fizz-buzz': true }
const { 'fizz-buzz': fizzBuzz } = foo
Now how do we remember this syntax? It's actually quite simple. You just have to think about the side of the assignment again.
When we create objects we have exactly the same syntax
const id = 1
const user = {
group: id
}
So if the object is on the right side of the assignment, we give the object user
a property group
that holds the variable id
.
If it is on the left side of the assignment it would be the opposite.
const { group: id } = user
We take the value of property group
and put it inside the variable id
.
Finally, computed object properties names
So the only thing left to explain is [key]
.
We can use this to access a computed property name, in our case the variable key
holds the value group
.
Again, nothing new here.
How do you add computed keys when creating objects?
Using the same syntax, just that it's on the right side of the assignment!
const key = 'group'
const id = 1
const user = {
[key]: id
}
But if we would just write let { [key] } = obj
under what name are we supposed to access this variable then? Well, we can't, so like with fizz-buzz
we need to assign it to a new variable using :
. This combination ultimately creates [key]: id
.
So that's that, how can we make it even more obscure? By applying a default value to the id!
Usually it would look like this
const user = { group: 1 }
const { group = 0, createdAt = null} = user
Using a computed property it becomes
let { [key]: id = 0, ...rest } = obj
References
Top comments (0)