Object destructuring in Javascript
In this article, you are going to learn all about object destructuring in Javascript. Previously we learned about Array destructuring in Javascript, so now let’s see how to destructure objects following pretty much the same syntax.
You’re also going to learn some cool tricks you can do using object destructuring like optional function arguments.
Object destructuring is a bit more complicated than array destructuring, but keep reading to the end and you’ll become a Javascript destructuring ninja in no-time!! 😄
Basics
When declaring an object, for example:
const employee = {
firstName: 'Bruce',
lastName: 'Lee',
role: 'Teacher',
};
We can say we packed the key-value pairs of data which we can retrieve at any point later by using the so-called “dot-syntax” and store the values of specific keys into variables, e.g.:
const firstName = employee.firstName;
const lastName = employee.lastName;
const role = employee.role;
We immediately see that this can get quite repetitive as the number of values we want to retrieve gets larger. Instead, we can utilize object destructuring to unpack the data we want and avoid repeating ourselves:
const { firstName, lastName, role } = employee;
console.log(firstName); // Bruce
console.log(lastName); // Lee
console.log(role); // Teacher
Unlike array destructuring, we should be more careful if we want to destructure into variables that have been declared before, so for example:
let firstName;
let lastName;
let role;
{ firstName, lastName, role } = employee; // Syntax Error !!
This will result in an error, since using curly braces outside of the context of declaring the variables ( let
), Javascript considers them to represent a code block instead. So, we should remember to explicitly wrap the whole expression using parenthesis ( … )
like this:
( { firstName, lastName, role } = employee )
console.log(firstName); // Bruce
// ... etc
Notice how we needed to remove the semicolon ;
after the employee
keyword in this case.
Missing elements
If we try to destructure an element which doesn’t exist in the object, the variable gets assigned the value of undefined
:
const employee = {
firstName: 'Bruce',
lastName: 'Lee',
role: 'Teacher',
};
let { firstName, middleName } = employee;
console.log(firstName); // Bruce
console.log(middleName); // undefined
You can also notice that we omitted to destructure some of the keys that exist in the employee
variable (lastName
and role
). Using object destructuring we have the liberty of choosing what we need to extract and safely ignoring the rest.
Changing the order
Changing the order of variables we are destructuring into doesn’t affect the destructuring in any way:
let { role, lastName, firstName } = employee;
console.log(role); // Teacher
// ...
This is possible because in JS objects store their value by named keys, unlike arrays by index where the order matters.
Renaming
But what if we want to use a different name for the variables we destructure the object into? A typical scenario for this is when you fetch data via an API call from the backend that might have a different naming convention for variables, or a different variable name just fits better into the current context.
Let’s check the following example:
const employee = {
first_name: 'Bruce',
last_name: 'Lee',
role: 'Teacher',
};
let {
first_name: firstName, // rename
last_name: lastName, // rename
role
} = employee;
console.log(firstName); // Bruce
console.log(lastName); // Lee
console.log(role); // Teacher
As you can see, we can use the colon :
operator to rename the variable we are extracting the data into.
An easy way to remember this is to imagine saying to yourself
this: into that
Default values
We’ve seen previously that if we try to destructure a value for a key in the object that doesn’t exist, we get undefined
:
const employee = {
firstName: 'Bruce',
lastName: 'Lee',
};
let { firstName, middleName } = employee;
console.log(middleName); // undefined
We can actually define a default value which will be assigned instead of undefined
:
let { firstName, middleName = 'The Dragon' } = employee;
console.log(middleName); // The Dragon
This technique is very useful for defining functions with optional arguments, as we are going to learn later.
It’s also possible to use both renaming and default values together for greater flexibility:
const employee = {
first_name: 'Bruce',
last_name: 'Lee',
role: 'Teacher',
};
// ...
let {
first_name: firstName,
middle_name: middleName = 'The Dragon',
} = employee;
console.log(firstName); // Bruce
console.log(middleName); // The Dragon
Dynamic object destructuring
Don’t get frightened by the title, it’s a mouthful, but actually, it boils down to this question:
How do we destructure an object property for which we don’t know the key name in advance?
In other words, the key name will be determined during runtime stored in a variable. Let’s see an example:
// the variable key can be set anywhere in the program
// i.e. stored as a function return value, etc.
let key = 'role';
const employee = {
first_name: 'Bruce',
last_name: 'Lee',
role: 'Teacher',
};
let {[key]: val} = employee;
console.log(val); // Teacher
We can see that we used the variable [key]
. The square brackets mean that the value stored in the variable key
should be used as a key when destructuring, which happens to be 'role'
in our case. The [key]
is called computed property name and can also be used when declaring objects.
Nested object destructuring
We can destructure complex nested objects which can contain arrays as well. In that case we can combine both object and array destructuring (see Array destructuring in Javascript) at the same time:
const employee = {
firstName: 'Bruce',
lastName: 'Lee',
role: 'Teacher',
addresses: [
{
street: 'Main street 9',
city: 'San Francisco',
country: 'USA',
},
{
street: 'Not main street 122',
city: 'Hong Kong',
country: 'China',
},
],
};
let {
lastName,
role: occupation,
addresses: [
{city: recentCity}, {city: oldCity}
]
} = employee;
console.log(lastName); // Lee
console.log(occupation); // Teacher
console.log(recentCity); // San Francisco
console.log(oldCity); // Hong Kong
We can see that we managed to “dig out” cities from the nested structure of the employee object by writing the destructuring pattern that matches that object. In this case, we only needed cities from addresses
objects so other values were ignored.
Tricks using object destructuring
Now that we learned the basics of object destructuring, let’s dive into some of the tricks we can perform using it.
Save the remaining properties as a new object
Let’s say we destructure only some of the properties of an object, but we want to store the rest (unprocessed properties) into a new object variable.
We can do this similarly to array destructuring using the spread ...
operator.
const userProfile = {
firstName: 'John',
lastName: 'Deere',
socialSecurityNo: 123
};
const { socialSecurityNo, ...rest } = userProfile; console.log(rest); // { firstName: 'John', lastName: 'Deere' }
Keep in mind that …rest
has to always be located at the end of destructuring, which means something like const { firstName, …rest, socialSecurityNo } = userProfile
would throw an error.
Destructuring of function arguments
So far, we’ve been destructuring objects using the assignment =
operator. But the destructuring syntax can also be used in the function definition, where we can define how an object is going to be destructured once we pass it to a function call as an argument.
const employee = {
firstName: 'Bruce',
lastName: 'Lee',
role: 'Teacher',
address: {
street: 'Main street 9',
city: 'San Francisco',
country: 'USA',
},
};
function processEmployee({ firstName, address: { city } }) {
console.log(firstName);
console.log(city);
}
// Now we call the function
processEmployee(employee);
// the function call prints the following:
// Bruce
// San Francisco
This is great because it means we don’t have to unpack the function input argument using the dot syntax in which case the function would look like this:
function processEmployee(employee) {
const firstName = employee.firstName;
const city = employee.address.city;
console.log(firstName);
console.log(city);
}
Yuck! As you can see, this ends up with more lines of code and ugly repetition when using the dot syntax. This issue gets increasingly annoying as we need more object properties.
But where object destructuring really shines is when we want to define a function with optional arguments.
Optional function arguments with default values
Let’s assume we have defined the following function for showing some information related to a project, it’s category and tasks associated to it:
function showProjectInfo(projectName, category, tasks) {
console.log(projectName);
console.log(category);
for (const task of tasks) {
console.log(task);
}
}
showProjectInfo(
'Array Destructuring',
'Blog',
['Write outline', 'Write article', 'Publish article']
);
/* Output shown: */
// Array Destructuring
// Blog
// Write outline
// Write article
// Publish article
And now let’s say that we want to make some of the arguments of this function optional, so for example we want this function to be able to only take in tasks
. If we don’t provide arguments projectName
and category
in the function call, we want to set the values of these arguments to Unknown
and Uncategorized
, respectively.
The first approach you might take is to use the regular default arguments syntax provided by Javascript:
function showProjectInfo(projectName='Unknown', category='Uncategorized', tasks) {
// ...
}
There are a couple of issues with this approach when calling this function:
- We need to know how many arguments this function takes
- We need to know what’s the order of arguments to pass in
- We cannot leave out a certain argument from the call
So if we wanted to call it by providing only tasks
and leaving the projectName
and category
to have the default values set, we would have to explicitly call the function with values for those arguments being undefined
:
showProjectInfo(undefined, undefined, ['Write outline', 'Write article', 'Publish article']);
/* Output shown: */
// Unknown
// Uncategorized
// Write outline
// Write article
// Publish article
Otherwise, if we called it using showProjectInfo(['Write outline', 'Write article', 'Publish article']);
we would get an error.
Fortunately, we can utilise the power of object destructuring with default values to solve this issue:
function showProjectInfo({
projectName: name = 'Unknown', // rename + default value
category = 'Uncategorized', // default value
tasks,
}) {
console.log(name); // `projectName` was renamed to `name`
console.log(category);
for (const task of tasks) {
console.log(task);
}
}
Now we can call this function by passing in an object with only the tasks
key set to the array of tasks:
showProjectInfo({ tasks: ['Write outline', 'Write article', 'Publish article'] });
/* Output shown: */
// Unknown
// Uncategorized
// Write outline
// Write article
// Publish article
Notice that we left out projectName
and category
from the call completely and the default values were assigned in the function. We can choose to set the category as well and leave out only projectName
. Additionally, we don’t even have to think about the order of these parameters during the function call:
showProjectInfo({
tasks: ['Write outline', 'Write article', 'Publish article'],
category: 'Blog',
});
/* Output shown: */
// Unknown
// Blog
// Write outline
// Write article
// Publish article
We can even choose to destructure the array tasks
provided as a function argument using array destructuring, for example let’s say we only wanted to capture maximum 2 first tasks:
function showProjectInfo({
projectName: name = 'Unknown',
category = 'Uncategorized',
tasks: [task1, task2], // first two tasks
}) {
console.log(name);
console.log(category);
console.log(task1);
console.log(task2);
}
Now if we call the function with e.g. 3 tasks we see we get only first 2 shown:
showProjectInfo({
tasks: ['Write outline', 'Write article', 'Publish article']
});
// Unknown
// Uncategorized
// Write outline
// Write article
Let’s now define the same function as before, but make all arguments optional by also specifying the tasks
array to have the default value of []
(an empty array):
function showProjectInfo({
projectName: name = 'Unknown',
category = 'Uncategorized',
tasks = [],
}) {
console.log(name);
console.log(category);
for (const task of tasks) {
console.log(task);
}
}
showProjectInfo({});
// Unknown
// Uncategorized
But, notice that we still need to remember to provide an empty object to the function call in this case. If we want to remove this requirement and be able to perform the call like showProjectInfo()
, we can use a trick, which is to add an empty object as a default value for the whole object we pass in:
function showProjectInfo({
projectName: name = 'Unknown',
category = 'Uncategorized',
tasks: [task1, task2] = [],
} = {}) { // We added "= {}" here !!
/// ...
}
Now we can call the function without providing any arguments, which makes the default value of {}
take effect and is the same as calling the function using it:
showProjectInfo();
// Unknown
// Uncategorized
showProjectInfo({});
// Unknown
// Uncategorized
Conclusion
That’s it! You’re now a Javascript destructuring ninja! Now go ahead and be DRY !!!.
If you need a refresher on the array destructuring in Javascript, take a look at my previous article on the topic here.