Object types are typically flexible and extensible, allowing for additional properties...
In TypeScript, object types are generally open and extendable. An object is classified as a Person
only if it contains a name
property of type string
. Such an object may have extra properties beyond just the name
property and still be considered a Person
. This feature is advantageous as it enables interface extensions to create type hierarchies:
interface AgedPerson extends Person {
age: number;
}
const agedPerson: AgedPerson = { name: "Alice", age: 35 };
const stillAPerson: Person = agedPerson; // valid
Thanks to TypeScript's structural type system, declaring an interface for AgedPerson
is not necessary for it to be recognized as a subtype of Person
:
const undeclaredButStillAgedPerson = { name: "Bob", age: 40 };
const andStillAPersonToo: Person = undeclaredButStillAgedPerson; // valid
Here, undeclaredButStillAgedPerson
and AgedPerson
share the same type {name: string, age: number}
, allowing the assignment to a Person
without issues.
Despite the convenience of open/extendable typing, it can be perplexing and may not always be desirable. There has been a request for TypeScript to support exact types, which would restrict a type like Exact<Person>
to only have a name
property and no other properties. An AgedPerson
would be a Person
but not an Exact<Person>
. As of now, there is no direct support for such exact types.
...however, object literals are subject to excess property checking.
Returning to the topic: while object types in TypeScript are generally open, there is a scenario where an object is treated as an exact type. This happens when assigning an object literal to a variable or passing it as a parameter.
Object literals are specially handled and undergo excess property checking when initially assigned to a variable or passed as a function argument. If the object literal contains properties not defined in the expected type, an error occurs, as shown below:
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}
fits the definition of a
Person
, it is not an
Exact<Person>
, hence the error. It's worth noting that the error specifically references "object literals".
In contrast, consider the following, which does not trigger an error:
const samePerson = { name: 'Jack', id: 209 }; // valid
person = samePerson; // valid
The assignment of the object literal to samePerson
is error-free because the type of samePerson
is inferred as
/* const samePerson: {
name: string;
id: number;
} */
and no excess properties are present. The subsequent assignment of samePerson
to person
also succeeds as samePerson
is not an object literal, thus exempt from excess property checks.
Playground link to code