DEV Community

Cover image for Destructuring JavaScript objects like a pro
Will Soares
Will Soares

Posted on

Destructuring JavaScript objects like a pro

Hello there!

For a long time now I've been wanting to take notes on a couple of tricks I currently use at work regarding the concept of Destructuring in JavaScript. I feel like most of the things I learn and am currently using in a daily basis will just fade out once I stop using them this often. Thus, I decided to write down those things in order to make them stick with me for longer even when I'm not looking at them daily. I used to do this when in middle and high school but stopped right after I started college and I feel the consequences now, specially considering I have a really shaky memory.

For those out there who are not familiar with the idea of destructuring in JavaScript, following is a brief overview of it. There are also tons of posts about it if you google it, so feel free to look for more content if this is too brief or not clear enough for you ;)

Destructuring was not always there in JavaScript for people to use, the concept was introduced to the language in June of 2015 together with a few other features that make up the 6th edition of the language, which is popularly known as ES6 or ES2015 (check this for reference).
The idea is basically to allow assignment of variables based on object properties or array values in a prettier manner. If you think of it as being the opposite idea of structuring something, which it is, you'll get that the object is being "broken down" into pieces until you find the value you want and then use that to create a variable.

Check the following code which shows one of the ways you would create a variable that is supposed to have a value contained in an object considering you don't know the existence of destructuring.

Note that classs is written like that in the entire text to avoid conflicts with the keyword class.

const homoSapiens = {
  kingdom: 'Animalia',
  classs: 'Mammalia',
  family: 'Hominidae',
  genus: 'Homo',
  species: 'H. sapiens'
}

const homoSapiensFamily = homoSapiens.family;

// and if you want to be certain of the type of the variable, you would
// set a default value for it in case the `family` property does not 
// exist in the source object
const safeHomoSapiensFamily = homoSapiens.family || '';

Enter fullscreen mode Exit fullscreen mode

You see that you'd have to do the same thing for each property that you want to use in that object, which is not really a huge pain to do but why should we do it that way when we can take advantage of the power of destructuring to both create variables and make sure of their type?
The following is a snippet that uses destructuring to accomplish the same.

const { family = '', species = '' } = homoSapiens;
Enter fullscreen mode Exit fullscreen mode

Here we are creating two variables called family and species based on properties that have the same name in the source object. And we are also making sure that they will be strings even when those two properties are not contained in the object.

You might argue that family and species are not really meaningful variable names if you look at them by themselves. Destructuring also allows us to specify the variable name (an alias) we want instead of using the name of the property in the object.

const {
  family: homoSapiensFamily = '',
  species: homoSapiensSpecies = ''
} = homoSapiens;
Enter fullscreen mode Exit fullscreen mode

Here we use the same values as before but now we're creating two variables called homoSapiensFamily and homoSapiensSpecies. Much more meaningful, right?

If you got the idea by now I believe you noticed you can go crazy about it and destructure real nested objects.

const homoSapiens = {
  classs: {
    name: 'Mammalia',
    super: {
      name: 'Tetrapoda'
    },
    sub: {
      name: 'Theria'
    }
  },
  species: 'H. sapiens'
};

const {
  classs: {
    super: {
      name: homoSapiensSuperClass = ''
    }
  }
} = homoSapiens;
Enter fullscreen mode Exit fullscreen mode

Here we created a variable named homoSapiensSuperClass which will have the value of Tetrapoda.

What if we try to destructure a nested object and at some point the property we specified does not exist?

// considering the previous homoSapiens object

const {
  classs: {
    infra: {
      name: homoSapiensInfraClass = ''
    }
  }
} = homoSapiens;
Enter fullscreen mode Exit fullscreen mode

If you try this you'll see that we get an error that says:

Uncaught TypeError: Cannot destructure property `name` of 'undefined' or 'null'.
Enter fullscreen mode Exit fullscreen mode

This happens because in the source object we don't really have an object called infra under the classs object. Thus, the homoSapiensInfraClass variable is never defined.

To avoid this you can set a default value for each property you are going through while destructuring an object. In this specific case, you would want to make sure that the default value for that infra property is an object, so you can keep destructuring it in case that property does not exist.

const {
  classs: {
    infra: {
      name: homoSapiensInfraClass = ''
    } = {}
  } = {}
} = homoSapiens;
Enter fullscreen mode Exit fullscreen mode

This way even though the homoSapiens object does not contain a property called infra you will still end up defining a variable called homoSapiensInfraClass which will receive the default value you set or undefined if you did not set a default value for it.

It also works with arrays!

The idea is basically the same with arrays, the difference, apart from the fact that syntax is a bit different, is that you cannot consider property names and instead will do things based on the order of items in the array.

const [first, second ] = ['a', 'b'];
// first will be 'a' and second will be 'b'

// you can also set default values
const [safeFirst = 'a', safeSecond = 'b'] = ['a']
// safeSecond here will have a value of 'b'
Enter fullscreen mode Exit fullscreen mode

It also works in a function signature!

You can also do destructuring in a function signature in order to expose only specific properties of the object being received to the function context.

const homoSapiens = {
  kingdom: 'Animalia',
  classs: 'Mammalia',
  family: 'Hominidae',
  genus: 'Homo',
  species: 'H. sapiens'
}

function logSpeciesInfo ({ species = '', kingdom = '', classs = '' }) {
  console.log(`The species ${species} belongs to the ${kingdom} kingdom and ${classs} class.' );
}

logSpeciesInfo(homoSapiens);
// Logs "The species H. sapiens belongs to the Animalia kingdom and Mammalia class."
Enter fullscreen mode Exit fullscreen mode

Any other property from the object that is not specified in the function header does not exist within the function body.

Can I do destructuring everywhere?

There is a really cool table in the Destructuring assignment page of MDN web docs that shows the current browser compatibility of this syntax. You can see that it is widely supported so compatibility shouldn't be an issue for you, unless...IE is a thing for you :)

destrucure_compatibility

Quiz

With what you learned in this post, are you able to use the power of destructuring to swap values of two variables without using any extra variable? Try before looking at comments ;)

Let me know in the comments! And if you have any other use cases for destructuring make sure to share that as well :D

Thanks for reading!

Discussion (26)

Collapse
n1ru4l profile image
Laurin Quast

You can even do this wizardry:

const object = { prop1: "value1", prop2: "value2" };

const  propName = "prop1";

// destructure dynamic property
const { [propName]: value } = object;

value === "value1" // true
Collapse
willamesoares profile image
Will Soares Author

Yes! Dynamic destructuring!
allthethings

Collapse
vitorreisdev profile image
Vitor Reis

js wizardry... That's pretty cool I haven't thought of that

Collapse
itspauloroberto profile image
Paulo Roberto Rosa

It is possible to do it nested???

Collapse
n1ru4l profile image
Laurin Quast

You mean the following?

const object = { prop1: "value1", prop2: { a: "abc" } };
const  propName1 = "prop2";
const propName2 = "a";
const { [propName1]: { [propName2]: value } } = object;
value === "abc" // true
Collapse
js2me profile image
Sergey S. Volkov • Edited on

Good article, thanks ! :)

But one moment

We have small but important difference between default value of destructed property and ||

const { foo = "bar" } = baz

In this case runtime compiler (as I know) checks value only on undefined and because of this some falsy value like false or '' is not been replaced to default value "bar"

But in other case

const foo = baz.foo || "bar"

|| checks first operand on any possible falsy value and if we have .foo like null then variable will been equals to "bar"

Collapse
andreyvladikin profile image
Andrey Vladikin

let a = 1;
let b = 2;
[a, b] = [b, a];
=)

Collapse
timkor profile image
Timkor

Nice, didn't realise one could easily use this to swap variables!

Collapse
willamesoares profile image
Will Soares Author • Edited on

Congrats :)
I should probably tell people not to look at comments before trying πŸ˜„

Collapse
merri profile image
Vesa Piittinen • Edited on

I'd advice against deeply nested destructures like the given example:

const {
  classs: {
    infra: {
      name: homoSapiensInfraClass = ''
    } = {}
  } = {}
} = homoSapiens;

There is classs there, which could be a typo and it would easily go totally unnoticed.

Another point I'd like to make is that it is very hard to read the above. Just do:

const homoSapiensInfraClass = homoSapiens['class'].infra.name || ''

Or if you want to allow all objects to miss:

import get from 'lodash/get'

const homoSapiensInfraClass = get(homoSapiens, 'class.infra.name', '')

Much easier to understand what is happening. If you want to get multiple items you could use array destructuring:

const [homoSapiensInfraClass, homoSapiensFamily] = [
    'class.infra.name',
    'family'
].map(item => get(homoSapiens, item, ''))

Although this makes sense only if you have a whole lot of those. This example is good because it retains the normal reading order, while renaming while destructuring fights against the normal reading order. Rename in destructuring should be used sparingly to keep code easy to read.

Collapse
willamesoares profile image
Will Soares Author

Hey Vesa, thank you for the suggestion. I appreciate your contribution.

As for your concern on legibility I would take that more as a preference matter, I personally don't see that as a very hard thing to read but I understand your point.

I think the option you gave it's nice if you want things to explicitly fail so you'd be aware of possible typos. In a context in which you don't know what to expect for the structure of the homoSapiens object for instance I'd prefer to be safe and set default values for each object being destructured, instead of receiving errors like Cannot read property 'x' of undefined.

And yeah, if you can use lodash then go for it ;)

Collapse
timkor profile image
Timkor

I think it's by purpose. class is a reserved keyword. Although possible to use in some modern browser, I bet IE doesn't like this.

Collapse
brittanmcg profile image
Brittan McGinnis

I would agree with you. I think that just because you can doesn't necessarily mean that you should. I do appreciate the knowledge sharing though and it is interesting to see the different ways that it's possible to destructure.

Collapse
agronick profile image
Kyle Agronick

Thats cool. I didn't know you could do that with the default values. It seems backwards to me though with the variable to assign from on the left of the colon and the variable to assign to on the right.

Collapse
c24w profile image
Chris Watson

I found that slightly counterintuitive to start with, too, when you're used to name = value. The easiest way I find to remember the order is { from: to } = obj. Also, it wouldn't make much sense to use { to: from } = obj because you're no longer putting the from in the same position as the key is in the object.

Collapse
alainvanhout profile image
Alain Van Hout

A very nice explanation 😁

Just because it makes my inner biologist cringe: could you possible replace 'specie' with 'species' (because that's both the singular and the plural form: en.wiktionary.org/wiki/species)?

Collapse
willamesoares profile image
Will Soares Author

Updated! Thanks for that :D

Collapse
laurieontech profile image
Laurie
Collapse
willamesoares profile image
Will Soares Author

Ah yeah! Great post! The more the merrier :)

Collapse
laurieontech profile image
Laurie

I mean, it’s clearly one of the best bits of modern ES syntax. Everyone must know!

Collapse
mehdibenhemdene profile image
mehdibenhemdene • Edited on

I don't know if anyone already posted this answer but from what I see must people used objects to do it instead of arrays.

My solution :

var foo = "foo";
var bar = "bar";

[bar, foo] = [foo, bar];
Collapse
mckabue profile image
Kabue Charles • Edited on

You actually don't need dynamic de-structuring to swap values of two variables without using any extra variable:

const { prop1: prop2, prop2: prop1 } = { prop1: "value1", prop2: "value2" }

console.log(prop1) // value2
console.log(prop2) // value1
Collapse
samba_code profile image
Sam Atkinson

Did not know you could destructure an array. Very cool! Thanks for the post.

Collapse
timea profile image
Timea Kiss

I have one serious issue with this article -> wanna give it more than one <3s :D Thanks!

Collapse
willamesoares profile image
Will Soares Author

Thanks Timea. I'm glad you liked it πŸŽ‰

Collapse
sergei profile image
Sergei Sarkisian

In the complex cases it seems just like another bad spaghetti code.

But in the simple cases it’s pretty usable and clean.