TypeScript interfaces do not strictly enforce properties when an object is assigned

Can someone help me understand TypeScript's rules for interfaces better? I am confused about why the following block of code throws an error due to the id property missing from the interface:

interface Person {
   name: string;
}

let person: Person = { name: 'Jack', id: 209 }

What puzzles me is why TypeScript does not throw an error when I try to add the id property by assigning another object like this:

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

Even though the id property is not defined in the interface, the person object ends up with the id property. Why does this happen?

Answer №1

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

Answer №2

Finally, TypeScript 4.9 has introduced the satisfies operator, providing a fix for this issue. Now, when defining a type like the one below:

type Person = {
    name: string,
    age: number
}

const person = { name: 'Jane Doe', age: 30 } satisfies Person

any attempt to add additional properties to the object will result in an error, ensuring strict adherence to the defined type structure.

Answer №3

Let's delve into the concept

An interface serves as a defined agreement that an object must adhere to.

Within interfaces, properties, methods, and events are specified as members. However, interfaces themselves only outline the structure of these members, leaving the actual implementation to the classes that inherit from them. This approach helps maintain a consistent blueprint for all deriving classes to follow.

Moreover, it's possible to implement multiple interfaces, offering implementers the flexibility to encompass features from different interfaces.

Although classes are obliged to comply with the interface contract, they can also introduce their unique implementations, a common practice in various programming languages.

Regarding TypeScript, as mentioned by @zerkms, it follows a structural subtyping approach for type compatibility assessment.

Structural typing evaluates types solely based on their members, establishing a correspondence between types.

In TypeScript's structural type system, the fundamental rule is that a type x can be assigned to a type y if y includes at least all the members present in x. For instance:

interface Pet {
  name: string;
}
let pet: Pet;
// dog's inferred type is { name: string; owner: string; }
let dog = { name: "Lassie", owner: "Rudd Weatherwax" };
pet = dog;

When assigning dog to pet, the compiler scrutinizes each property of pet to ensure a matching property exists in dog. As long as dog contains a member named name of type string, the assignment is considered valid.

Reference: type-compatibility

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Delete an essential attribute from an entity

I am trying to remove a required property called hash from an object, but I keep encountering TypeScript or ESLint errors. All the properties of the interface are mandatory, and I do not want to make all properties optional using Partial. Here is the inte ...

Ensuring type safety in React using TypeScript

Within the code snippet below, I have specified that setLocale should be passed a value of type Locale through LocaleContextValue. However, why does the setLocale function not throw an error if no value is provided as a parameter? Even when I change it t ...

Using Typescript to incorporate Next.js on Firebase Functions

I'm currently working on deploying a Next.js application to Firebase Functions. import next from 'next' import {https} from 'firebase-functions' const server = next({ dev: process.env.NODE_ENV !== 'production', conf: ...

Unable to access the values of the object within the form

I am encountering an issue where I cannot retrieve object values in the form for editing/updating. The specific error message is as follows: ERROR TypeError: Cannot read properties of undefined (reading 'productName') at UpdateProductComponen ...

Observe the task while returning - Firebase Functions

I am working on a Firebase Cloud Functions project where I need to run a scraping task within one of the functions. While the scraping is in progress, I also need to provide progress updates to the consumer. For example, informing them when the task is at ...

Use Typescript to access and utilize the value stored in local storage by using the

Trying to retrieve the language setting from localHost and implement it in a translation pipe as shown below: transform(value: string): string {... localStorage.setItem("language", JSON.stringify("en")); let language = JSON.parse(loca ...

Creating a Dynamic Example in Scenario Outline Using Typescript and Cypress-Cucumber-Preprocessor

I have a question that is closely related to the following topic: Behave: Writing a Scenario Outline with dynamic examples. The main difference is that I am not using Python for my Gherkin scenarios. Instead, I manage them with Cypress (utilizing the cypre ...

Dev error occurs due to a change in Angular2 pipe causing the message "has changed after it was checked"

I understand the reason for this error being thrown, but I am struggling with organizing my code to resolve it. Here is the problem: @Component({ selector: 'article', templateUrl: 'article.html', moduleId: module.id, di ...

Universal function for selecting object properties

I've recently delved into TypeScript coding and have run into a puzzling issue that has me stumped. Take a look at the code snippet below: interface testInterface { a: string; b: number; c?: number; } const testObject: testInterface = { a: & ...

The AngularJS Service fails to properly convert incoming Json Responses into Model objects during piping

I have been studying AngularJS 17 and recently developed a login application. However, I am facing an issue where the server response is not being properly mapped to the object in the Model Class. Response: { "id": 1, "userName& ...

"This error message states that the use of an import statement outside a module is not allowed

After searching for a solution without any luck, I decided to start a new discussion on this topic. Currently, I am working on azure functions using Typescript and encountering the following error: import { Entity, BaseEntity, PrimaryColumn, Column, Many ...

Crack encrypted information using Typescript after it was encoded in Python

I've encountered an issue while attempting to decrypt previously encrypted data in Python. Here is how I encrypt the data in Python: iv = Random.new().read( AES.block_size ) cipher = AES.new(secret_key, AES.MODE_CBC, iv) encrypdata = base64.b64enco ...

Having trouble installing the gecko driver for running protractor test scripts on Firefox browser

Looking to expand my skills with the "Protractor tool", I've successfully run test scripts in the "Chrome" browser. Now, I'm ready to tackle running tests in "Firefox," but I know I need to install the "gecko driver." Can anyone guide me on how t ...

Exploring the behavior of control flow in Typescript

I am a beginner when it comes to JS, TS, and Angular, and I have encountered an issue with an Angular component that I am working on: export class AdminProductsMenuComponent implements OnInit{ constructor(private productService: ProductService, ...

Circular dependency has been detected when using the ESLint with TypeORM

Having two entity typeorm with a bi-directional one-to-one relationship: Departament: @Entity('Departament') export default class Departament { @PrimaryGeneratedColumn() id: string; @Column() departament_name: string; @OneToOne(type ...

The function signature '() => void' cannot be assigned to a variable of type 'string'

Encountering an issue in Typescript where I am attempting to comprehend the declaration of src={close} inside ItemProps{}. The error message received reads: Type '() => void' is not assignable to type 'string'. Regrettably, I am ...

Dynamic Subscriptions in Angular 8/9: A guide to automatically using forkJoin with Observables from a dynamic collection

Initiate First Step: Envision an entity named RouteObservables residing somewhere within the project that I aim to utilize (import) across multiple components: export const RouteObservables = { state$: 'this.route.paramMap.pipe(map(() => window ...

Optimizing Angular for search engines: step-by-step guide

Regarding Angular SEO, I have a question about setting meta tags in the constructors of .ts files. I have implemented the following code: //To set the page title this.titleServ.setTitle("PAGE TITLE") //To set the meta description this.meta.addTag ...

Determine whether an object possesses a property of a specific data type

I am currently exploring the use of generics in converting a date split into multiple parts into a Date object. Here is what I have so far: export const convertDate = <T, K extends keyof T>(obj: T, key: K) => { const k = String(key) const [mo ...

Encountering an Unknown Error when attempting to retrieve a response using Angular's httpClient with

The Service.ts file contains the following code: public welcome(token: any){ let tokenString = "Bearer "+token console.log("tokenString is: "+tokenString) let header = new HttpHeaders().set("Authorization",tokenSt ...