loading...
Cover image for Destructuring JavaScript objects like a pro

Destructuring JavaScript objects like a pro

willamesoares profile image Will Soares ・5 min read

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 || '';

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;

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;

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;

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;

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

Uncaught TypeError: Cannot destructure property `name` of 'undefined' or 'null'.

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;

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'

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."

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!

Posted on Jun 16 '19 by:

willamesoares profile

Will Soares

@willamesoares

Software Developer πŸ’» "[...] relentlessly craving wanderlust." 🌎

Discussion

markdown guide
 

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
 

Yes! Dynamic destructuring!
allthethings

 

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

 
 

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
 

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"

 

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

 

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

 

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

 

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.

 

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 ;)

 

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.

 

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.

 

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];
 
 

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

 

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

 

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)?

 

Updated! Thanks for that :D

 

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.

 

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.

 

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
 

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

 

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

 

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

 

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

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