TypeScript interfaces not enforcing properties when an object is assigned

Object types are generally open and extendible, and allow extra properties…

Object types in TypeScript are generally open and extendible. An object is a Person if and only if it has a name property of type string. Such an object may turn out to have additional properties, but it’s still a Person. This is very useful and allows interface extensions to form type hierarchies:

interface AgedPerson extends Person {
  age: number;
}

const agedPerson: AgedPerson = { name: "Alice", age: 35 };
const stillAPerson: Person = agedPerson; // okay

And because TypeScript has a structural type system, you don’t actually have to declare an interface for AgedPerson for it to be seen as a subtype of Person:

const undeclaredButStillAgedPerson = { name: "Bob", age: 40 };
const andStillAPersonToo: Person = undeclaredButStillAgedPerson; // okay

Here undeclaredButStillAgedPerson has a type of {name: string, age: number}, equivalent to AgedPerson, and the subsequent assignment to a Person works for the same reason.


Even though open/extendible typing is useful, it can be confusing and is sometimes not desired. There is a longstanding open request at microsoft/TypeScript#12936 for TypeScript to support so-called exact types, where something like Exact<Person> would only be allowed to have a name property and nothing else. An AgedPerson would be a Person but not an Exact<Person>. There is currently no direct support for such exact types.


…but object literals do undergo excess property checking.

Jumping back: object types in TypeScript are generally open. But there is one situation where an object will be treated as if its type were exact. And this is when you assign an object literal to a variable or pass it as an argument.

Object literals get special treatment and undergo excess property checking when first assigned to a variable or passed as a function argument. If the object literal has properties not known to exist in the expected type, there’s an error. Like this:

let person: Person = { name: 'Jack', id: 209 }; // error!
// ------------------------------->  ~~~~~~~
// Object literal may only specify known properties, 
// and 'id' does not exist in type 'Person'.

Even though {name: "Jack", id: 209} is a Person by the original definition, it’s not an Exact<Person>, and so we get an error. Please note that the error specifically mentions “object literals”.


Contrast this to the following, where there is no error:

const samePerson = { name: 'Jack', id: 209 }; // okay
person = samePerson; // okay

The assignment of the object literal to samePerson is not in error because samePerson‘s type is inferred to be of the type

/* const samePerson: {
    name: string;
    id: number;
} */

and there’s no excess property there. The subsequent assignment of samePerson to person also succeeds, because samePerson is not an object literal, and therefore excess property checking does not apply.


Playground link to code

Leave a Comment