TypeScript: Excess Property Checks

Last Updated on

When is it helpful?

Let's say that we have a Dog interface

interface Dog {
  legs?: number;
}

and a function that accepts Dog as an argument:

function describeDog(dog: Dog) {
  console.log(`dog has: ${Object.keys(dog)}`);
}

We are supposed to use it like this:

describeDog({ legs: 4 });

Now, let's try to do something stupid:

describeDog({ legs: 2, wings: 2 });

This obviously doesn't work, because dogs don't have wings, we get an error:

Argument of type '{ legs: number; wings: number; }' is not assignable to parameter of type 'Dog'.
  Object literal may only specify known properties, and 'wings' does not exist in type 'Dog'.

Makes total sense, and that's why TypeScript is amazing (yay!)

What to avoid?

Of course, we can shoot ourselves in a foot by creating a flying dog:

describeDog({ legs: 2, wings: 2 } as Dog);

but thankfully we have @typescript-eslint/consistent-type-assertions rule to prevent this, and you should use it in your .eslintrc:

{
  "plugins": ["@typescript-eslint"],
  "rules": {
    "@typescript-eslint/consistent-type-assertions": [
      "error",
      {
        "assertionStyle": "never"
      }
    ]
  }
}

Also, you can Suppress Excess Property Errors, make sure that you don't do that.

When doesn't it work?

However, if we do something as seemingly innocent as moving an object into a variable:

const unknownCreature = {
  legs: 2,
  wings: 2,
};

describeDog(unknownCreature);

Guess what? All of a sudden, TS forgets about excess properties and says that "dog has: legs,wings" which obviously isn't right because a dog isn't supposed to have wings...

To prevent this, we can explicitly annotate variable type upon declaration:

const unknownCreature: Dog = {
  legs: 2,
  wings: 2,
};

Now this will throw an error as expected

Type '{ legs: number; wings: number; }' is not assignable to type 'Dog'.
  Object literal may only specify known properties, and 'wings' does not exist in type 'Dog'.

And we're saved, yay!

How to make it always work?

What if we forget to annotate variable type explicitly?
Don't worry, there's a @typescript-eslint/typedef rule, which has variable-declaration option that can help us:

{
  "plugins": ["@typescript-eslint"],
  "rules": {
    "@typescript-eslint/typedef": [
      "error",
      {
        "variableDeclaration": true
      }
    ]
  }
}

So now if we try to build a beast:

const unknownCreature = {
  legs: 2,
  wings: 2,
};

it won't let us:

Expected unknownCreature to have a type annotation.

But there are downsides:

Summary


Please, share (Tweet) this article if you found it helpful or entertaining!

Read more about Excess Property Checks (ignore that page is deprecated, this particular part still applies at the time of writing)