DEV Community

Dinos Vlachantonis
Dinos Vlachantonis

Posted on

Typescript's excess property check limitations

The obvious

In Typescript, when we are trying to assign an object literal to a typed variable, it is reasonable to expect Typescript to check whether there are any extra properties that are not defined in the respective type.

interface Doggo {
  name: string;
  likesTreatos: boolean;
}

const max: Doggo = {
  name: 'Max',
  likesTreatos: true,
  age: 4
  /**
  Type '{ name: string; likesTreatos: true; age: number; }'
  is not assignable to type 'Doggo'.
  Object literal may only specify known properties, and 'age'
  does not exist in type 'Doggo'
  */
}
Enter fullscreen mode Exit fullscreen mode

The not so obvious

Alright, let's tweak that piece of code a little bit and see how Typescript reacts.

interface Doggo {
  name: string;
  likesTreatos: boolean;
}

const dog = {
  name: 'Max',
  likesTreatos: true,
  age: 4
}

const max: Doggo = dog // no errors
Enter fullscreen mode Exit fullscreen mode

Wait a second, the value of dog that we are trying to assign to our max is not so different than before, yet Typescript doesn't think that's an issue this time.

The triggers of the excess property check

Typescript does not complain because there is no excess property check triggered. The reason why is because it will only trigger on assignments that involve object literals, and dog is clearly not one.

On the first example above, our max variable was actually assigned to an object literal, triggering the check which then threw an error.

We can see a similar behavior when passing function parameters.

interface Doggo {
  name: string;
  likesTreatos: boolean;
}

function giveSnack(doggo: Doggo) {
  console.log(`Snack given to ${doggo.name}`)
}

giveSnack({
  name: 'Max',
  likesTreatos: true,
  age: 4
  /**
  Argument of type '{ name: string; likesTreatos: true; age: number; }'
  is not assignable to parameter of type 'Doggo'.
  Object literal may only specify known properties, and 'age'
  does not exist in type 'Doggo'
  */
})

const dog = {
  name: 'Max',
  likesTreatos: true,
  age: 4
}

giveSnack(dog) // no errors
Enter fullscreen mode Exit fullscreen mode

But still, why would that not fail..?

It was hard for me to initially accept the fact that Typescript would allow an assignment like that.
But actually, as far as Typescript's structural type system is concerned, this is a totally valid scenario.

Let's take a step back.

interface Doggo {
  name: string;
  isGoodBoy?: boolean;
}
Enter fullscreen mode Exit fullscreen mode

How big is the domain of values for the Doggo type?
The answer is, pretty huge!
Basically any object that has a name property that is a string and any other property besides a non-boolean isGoodBoy is a valid Doggo.

Based on that, here is a surprisingly valid assignment to a Doggo typed constant:

interface Doggo {
  name: string;
  isGoodBoy?: boolean;
}

const max: Doggo = window
Enter fullscreen mode Exit fullscreen mode

window has a name property which is a string and does not have a non-boolean isGoodBoy property (even though it'd be cool).

Going back to our previous example

interface Doggo {
  name: string;
  likesTreatos: boolean;
}

const dog = {
  name: 'max',
  likesTreatos: true,
  age: 4
}

const max: Doggo = dog // no errors
Enter fullscreen mode Exit fullscreen mode

This starts to make a bit more sense now, the variable dog is inferred as {name: string, likesTreatos: boolean, age: number} which is included in the domain of values of the type Doggo and hence being a valid assignment.
On top of that, the excess property check is not triggered because we are not dealing with object literals.

Final thoughts

One could say that Typescript's structural type checker focuses more on preventing us from writing code that would throw errors during runtime, while the excess property checking tries to identify code which might not be what we intended.

Top comments (1)

Collapse
 
vivekdoshi profile image
vivek-doshi-genea

Just created an account to like this post.
Best explanation!