Functions and generics
Object destructuring with types in TypeScript
Learn the correct TypeScript syntax for adding types to object destructuring, why name: string fails, and how a dedicated type or interface keeps it clean.
I was using TypeScript in Deno to build a sample project and I had to destructure an object. I am familiar with TypeScript basics but sometimes I hit a problem.
Object destructuring was one of those.
I wanted to do
const { name, age } = body.value
I tried adding the string and number types like this:
const { name: string, age: number } = body.value
But this didn’t work. It apparently worked, but in reality this is assigning the name property to the string variable, and the age property value to the number variable.
That is because a colon inside a destructuring pattern already has a meaning in JavaScript: renaming. { name: string } means “take the name property and put it in a variable called string”. No error, no types, just two badly named variables. You can prove it:
const { name: string, age: number } = body.value
console.log(string, number)
This compiles and prints the two values, Jack 3 for a dog named Jack. That is what makes the mistake sneaky. The code runs, and the compiler has nothing to complain about, because you wrote valid JavaScript.
The correct syntax is this:
const { name, age }: { name: string; age: number } = body.value
The annotation goes after the whole pattern, and it types the object being destructured, not the individual variables. TypeScript then works out that name is a string and age is a number from the object type.
That inline object type gets noisy fast. The best way to approach this is to create a type or interface for the data:
interface Dog {
name: string
age: number
}
Then you can write the above in this way, which is shorter:
const dog: Dog = body.value
And once the value is typed, destructuring needs no annotation at all:
const { name, age } = dog
TypeScript already knows the shape of dog, so name and age get their types by inference. This is the version I end up with most of the time. Type the value once, destructure freely afterwards.
The same pattern-then-annotation syntax works in function parameters, where destructuring is very common:
function describeDog({ name, age }: Dog) {
return `${name} is ${age} years old`
}
Again the type applies to the whole parameter object, and the destructured variables are inferred from it. Call describeDog(dog) and you get 'Jack is 3 years old'.
If you ever see : string inside a destructuring pattern in a code review, now you know what to look for. Check whether a variable called string shows up a few lines later.
Lesson completed