Object destructuring in Javascript

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:

  1. We need to know how many arguments this function takes
  2. We need to know what’s the order of arguments to pass in
  3. 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.