Object destructuring

Destructuring assignment

The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables. The destructuring assignment uses similar syntax to the object and array literal, except on the left-hand side of the assignment a package of variables that define what values to assign from the right-hand side array or object.


const myArray = [1, 2, 3, 4, 5];
const [x, y] = myArray;

const myObject = { a: 1, b: 2 };
const {a, b} = myObject;

// is equivalent to:
// const x = myArray[0];
// const y = myArray[1];
// const a = myObject.a;
// const b = myObject.b;

console.log(x); // logs: 1
console.log(y); // logs: 2
console.log(a); // logs: 1
console.log(b); // logs: 2

Binding and assignment pattern

In the above example the assignment starts with a declaration keyword; const in this case. All variables, or constants in this case, share the same declaration. This is called a binding pattern. It is also possible to assign to variables or properties declared elsewhere in the code. In this case the assignment does not start with a declaration keyword and is called a assignment pattern.


const myObject = { a: 1, b: 2 };
let a, b;

({ a, b } = myObject);

// is equivalent to:
// let a, b;
// a = myObject.a;
// b = myObject.b;

console.log(a); // logs: 1
console.log(b); // logs: 2

Parentheses () around the destructuring assignment statement are required when using an object literal an no declaration keyword. Without parentheses, { a, b } would be interpreted as a block statement and not as an object literal.

Destructuring and the prototype chain

When deconstructing an object, if a property cannot be matched with an own property, it will continue to look for a match along the prototype chain. Array deconstructing uses the iterator instead of enumerating properties. Therefore, array deconstructing will not look for elements in the prototype chain.


const myObject = {
  a: 1,
  __proto__: { b: 2, },
};

const { a, b } = myObject;

console.log(a); // logs: 1
console.log(b); // logs: 2 // from the prototype chain

Default value

Each destructured element or property can be appointed a default value. The default value gets assigned only when the element or property is not present or when it has value undefined. The default value can be any expression.


const [x = "Default value"] = [];
console.log(x);  // logs: "Default value"

const { a = 2 } = { a: undefined };
console.log(a); // logs: 2

const { b = 2 } = { b: null };
console.log(b); // logs: null

Rest property

Rest syntax can be used in a destructuring assignment statement. The rest property or rest element gets a new object or array assigned in which all remaining own properties or elements will be stored. The rest property must be the last in the pattern.


const { a, ...restProperty } = { a: 1, b: 2, c: 3 };
console.log(a); // logs: 1
console.log(restProperty); // logs: { b: 2, c: 3 }

const [first, ...restArray] = [1, 2, 3];
console.log(first); // logs: 1
console.log(restArray); // logs: [2, 3]

Note that the rest property basically creates a (altered) shallow copy of an object or array. In the next example the rest property is used to create a shallow copy of object myObject (only own properties, prototype and inherited properties are not copied).


const myObject = {
    een: 'one',
    twee: 'two',
    drie: 'three'
}

const { ...copiedObject } = myObject;

console.log(copiedObject);  // logs: { een: "one", twee: "two", drie: "three" }

Object vs array destructuring

Array destructuring and object destructuring do not work exactly the same. For instance, array destructuring calls the iterator of the right-hand side and object destructuring uses property enumeration on the right-hand side. With array destructuring, the values of elements from the right-hand side are assigned to variables in the left-hand side, both in the same order. With object destructuring, assignment goes by property name, not by order. In the next example this difference is clarified by skipping the second property in an array destructuring and in an object destructuring. Array destructuring will be covered in more detail in a later chapter.


const myArray = [1, 2, 3];
const [x, , y] = myArray;

const myObject = { a: 1, b: 2, c: 3 };
const {a, c} = myObject;

console.log(x); // logs: 1
console.log(y); // logs: 3

console.log(a); // logs: 1
// console.log(b); // ReferenceError: b is not defined
console.log(c); // logs: 3

Assigning to new variable names

So, with object destructuring, assignment goes by property name. However, a property can be assigned to a variable with a different name than the object property.


let { a: foo, b: bar } = { a: true, b: false };

console.log(foo); // logs: true
console.log(bar); // logs: false

Now the variables are property values foo and bar instead of property names a and b, but the destructuring still connects via the property names. Next an example with default values.


const { a: foo = 10, b: bar = 20 } = { a: 30 };

console.log(foo); // 30
console.log(bar); // 20

When using the assignment pattern, the variables need to be declared, not the property names:


const myObject = { a: 1, b: 2 };
let a, c; // b does not need to be declared

({ a, b: c } = myObject);

// is equivalent to:
// let a, c;
// a = myObject.a;
// c = myObject.b;

console.log(a); // logs: 1
console.log(c); // logs: 2

PS. Note that in non-strict mode also c does not need to be declared. JavaScript will, in that case, automatically create an implicit global c. Not declaring c in strict mode will throw an error.

Note that when the property name is an invalid JavaScript identifier, you must assign to an alternative valid identifier.


const { 'foo-bar': fooBar, fizzBuzz } = { 'foo-bar': true, fizzBuzz: false };

console.log(fooBar); // logs: true
console.log(fizzBuzz); // logs: false

// SyntaxError:
const { foo-bar, fizzBuzz } = { 'foo-bar': true, fizzBuzz: false }; // SyntaxError
const { 'foo-bar', fizzBuzz } = { 'foo-bar': true, fizzBuzz: false }; // SyntaxError

This also holds for computed object property names. In the example below, 'a' (a string value) is not a valid JavaScript identifier:


// SyntaxError:
const key = 'a';
const { [key] } = { a: 'foo' }; // SyntaxError: missing : after property id
console.log(a);

const key = 'a';
const { [key]: a } = { a: 'foo' };
console.log(a); // logs: "foo"

Nested object and array destructuring


const myObject = { a: 1, b: { c: 2 } };

const { a, b: { c } } = myObject;
// is equivalent to:
// a = myObject.a;
// c = myObject.b.c;

console.log(a); // logs: 1
//console.log(b); //  ReferenceError: b is not defined
console.log(c); // logs: 2

const myObject = { a: 1, b: { c: 2 } };

const { a, b: { c: d } } = myObject;
// is equivalent to:
// a = myObject.a;
// d = myObject.b.c;		

console.log(a); // logs: 1
console.log(d); // logs: 2
console.log(myObject.b.c); // logs: 2

let a, d;

({ a, b: { c: d } } = { a: 1, b: { c: 2 } });

console.log(a); // logs: 1
console.log(d); // logs: 2

Also arrays nested in an object can be destructured.


const somePerson = {
  fullName: { firstName: "Jane", LastName: "Doe" },
  degrees: ["BSc", "MSc", "PhD"]
}

const {
  fullName: { firstName: name },
  degrees: [ , secondDegree, ]
} = somePerson;

console.log(`The second degree ${name} received is a ${secondDegree} degree.`);
// logs: "The second degree Jane received is a MSc degree."

...or vice versa:


const users = [
  { id: 1, name: 'Jane'},
  { id: 2, name: 'Joe'},
  { id: 3, name: 'Jill'}
];

const [,, { name }] = users;

console.log(name); // logs: "Jill"

Assigning to an element or property

With assignment patterns you can assign a value to a property or element instead of to a variable or constant. So, an assignment pattern can be used to alter an object or array.


const myObject = { a: 1, b: 2 };
let a; const c = {};

({ a, b: c.d } = myObject);

// is equivalent to:
// let a; const c = {};
// a = myObject.a;
// c.d = myObject.b;

console.log(a); // logs: 1
console.log(c); // logs: { d: 2 }

const myObject = { a: 1, b: 2 };
let a; const c = [];

({ a, b:c[0] } = myObject);

// is equivalent to:
// let a; const c = [];
// a = myObject.a;
// c[0] = myObject.b;

console.log(a); // logs: 1
console.log(c); // logs: [2]

const myObject = {a: 1, b: 2};
({foo: myObject.a, bar: myObject.b} = {foo: myObject.b, bar: 3});
console.log(myObject); // logs: { a: 2, b: 3 }

Destructured function parameter

Function parameters can be objects to be unpacked into local variables via the destructuring assignment. These local variables (local to the function) may be accessed within the function body.


const myObject = { foo: 13, bar: "abc" }

function myFunction({ foo }) {
  return foo ** 2;
}

console.log(myFunction(myObject)); // logs: 169

...which is equivalent to:


const myObject = { foo: 13, bar: "abc" }

function myFunction(myObj) {
  const { foo } = myObj;
  return foo ** 2;
}

console.log(myFunction(myObject)); // logs: 169

Also in this case we can assign a property to a variable with a different name than the object property, and objects can be nested:


const myObject = { foo: 13, bar: "abc" }

function myFunction({ foo: squareMe }) {
  return squareMe ** 2;
}

console.log(myFunction(myObject)); // logs: 169

const somePerson = {
  fullName: { firstName: "Jane", LastName: "Doe" },
  birthDay: new Date("1990-12-06T04:34:00")
}

function displayDateOfBirth({ birthDay, fullName: {firstName: name} }) {
  return `${name} was born at ${birthDay.toUTCString()}`;
}

console.log(displayDateOfBirth(somePerson)); // logs: "Jane was born at Thu, 06 Dec 1990 03:34:00 GMT"

If the right-hand side of an object destructuring assignment is not an object but a primitive, it will get boxed into the corresponding wrapper object and the property is accessed on the wrapper object. But if the right-hand side of a destructured assignment is missing, it will throw an error. Hence, in the above example, calling the function without an argument will throw a type error. We can avoid this by providing a default value for the parameter. In the example below the default parameter value is an empty object.


const number13 = { value: 13, name: "thirteen" }
const number14 = { value: 14 }

function squareValue({ value } = {}) {
  return value ** 2;
}

function getNumberName({ name = "no name supplied" } = {}) {
  return name;
}

console.log(squareValue(number13)); // logs: 169
console.log(squareValue()); // logs: NaN

console.log(getNumberName(number13)); // logs: "thirteen"
console.log(getNumberName(number14)); // logs: "no name supplied"
console.log(getNumberName()); // logs: "no name supplied"

Next example shows a function filter to shallow copy an object but with skipping one property. It uses a computed object property name key.


const myObject = {
    een: 'one',
    twee: 'two',
    drie: 'three'
}

const filter = (key, { [key]: deletedKey, ...newObject } = {}) => newObject;

console.log(filter('twee', myObject)); // logs: { een: "one", drie: "three" }

for...of iteration and destructuring

A for...of loop can be used to iterate over an iterable and destruct each element.


const people = [
  {
    title: "Miss", 
    fullName: { firstName: "Jane", lastName: "Doe" },
    age: 23,
  },
  {
    title: "Mr",
    fullName: { firstName: "John", lastName: "Smith" },
    age: 29,
  }
];

for (const { title, fullName: { lastName } } of people) {
  console.log(`${title} ${lastName}`);
}
// logs:
// "Miss Doe"
// "Mr Smith"

// This is equivalent to:
for (const individual of people) {
  const { title, fullName: { lastName } } = individual;
  console.log(`${title} ${lastName}`);
}
// logs:
// "Miss Doe"
// "Mr Smith"