Destructuring from Nullable Objects in TypeScript

avatar

Borislav Hadzhiev

Last updated: Mar 9, 2022

banner

Photo from Unsplash

Destructuring from Nullable Objects in TypeScript #

Use the logical OR operator to destructure from a nullable object in TypeScript, e.g. const { name, age } = obj || ({} as Person). The logical OR operator is used to provide a fallback in case the object has a value of null or undefined.

index.ts
type Person = { name?: string; age?: number; }; const obj = null; const { name, age } = obj || ({} as Person); console.log(name); // 👉️ undefined console.log(age); // 👉️ undefined if (name !== undefined) { // 👇️ name is type string here console.log(name.toUpperCase()); }

The example shows how to destructure from a nullable object without using default values.

The obj variable could store a null or undefined value, so we used the logical OR (||) operator to provide an empty object as a fallback.

However, TypeScript won't let us destructure properties from an empty object, so we had to use a type assertion to type the object.

When you destructure a property that doesn't exist in an object, you get an undefined value back.

That's why we've typed the properties in the Person type to be optional - because of the possibility they will have undefined values if we destructure from an empty object.

An even better way would be to use the Partial utility type to achieve this result.

index.ts
type Person = { name: string; age: number; }; const obj = null; // 👇️ using Partial<Type> const { name, age } = obj || ({} as Partial<Person>); console.log(name); // 👉️ undefined console.log(age); // 👉️ undefined if (name !== undefined) { // 👇️ name is type string here console.log(name.toUpperCase()); }
The Partial utility type constructs a new type with all properties of the type set to optional (could be undefined).

This way we are forced to use a type guard to check if the specific value is NOT undefined before accessing built-in methods on it, or assume it is of specific type.

The if statement in the example serves as a type guard and checks if the value is not undefined.

Once we're in the if block the name variable is guaranteed to store a string.

You can also use default values when destructuring.

index.ts
type Person = { name?: string; age?: number; }; const obj = null; // 👇️ using default values const { name = 'James', age = 30 } = obj || ({} as Person); console.log(name); // 👉️ "James" console.log(age); // 👉️ 30 console.log(name.toUpperCase()); // 👉️ "JAMES"

If your use case allows you to use default values, it is quite useful because the type of the name variable is guaranteed to be a string, even though the name property is marked as optional in the Person type.

So if you could, for example, set an empty string default values for your strings, you don't have to use a type guard to know the value is a string.

If the object you are destructuring from has nested properties, you have to use a default value of an empty object to be able to access them.

index.ts
type Person = { name?: string; age?: number; address?: { country: string; }; }; const obj = null; const { // 👇️ default value of empty object (for nested properties) address: { country } = {}, name = 'James', age = 30, } = obj || ({} as Person); console.log(name); // 👉️ "James" console.log(age); // 👉️ 30 console.log(country); // 👉️ undefined if (country !== undefined) { console.log(country.toUpperCase()); }

The Person type has an address property that is an object with a country property.

To get around this, we passed an empty object as a default value when destructuring from the address object.

Now the type of the country variable is string or undefined, so we have to use a type guard to make sure it's a string.

Alternatively, you could provide a default value for the country variable as well.

index.ts
type Person = { name?: string; age?: number; address?: { country: string; }; }; const obj = null; const { address: { country = '' } = {}, name = 'James', age = 30, } = obj || ({} as Person); console.log(name); // 👉️ "James" console.log(age); // 👉️ 30 console.log(country.toUpperCase()); // 👉️ ""

We passed an empty string as the default value for the country variable. The benefit of doing this is that it is now guaranteed to be a string, so no type guards are needed.

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.