Prevent the assignment of objects with additional properties than specified in the target interface using Typescript

Imagine I have a standard 'user' object that includes the common properties like username, email, and password. Now, I want to create a separate object that is a subset of the user object but without the password property. Here's a basic example:

interface IUserSansPassword {
    username: string;
    email: string;
}

class UserSansPassword implements IUserSansPassword { ... }

interface IUser extends IUserSansPassword {
    password: string;
}

class User implements IUser { ... }

When attempting to assign an object of type IUserSansPassword, I would expect the following code to result in an error:

const userSansPassword: UserSansPassword = new User(); // No TS Error †††

To my surprise, TypeScript does not produce an error in this case because it allows objects with extra properties to be assigned. This behavior contrasts with defining the object directly with the extra property as shown below:

const userSansPassword: IUserSansPassword = {
    username: 'jake',
    email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="680209030d281b0609030d460b07[email protected]">',
    password: '' // TS Error ***
}

In summary, my questions are:

  1. Why does TypeScript behave this way? Is it not problematic to allow assignment to a type with excess properties (as seen in *** above)?

  2. Is there a specific TypeScript setting or method that can be used to generate an error for cases like ††† above?

Answer №1

The responses provided by other users are essentially accurate: types in TypeScript are typically open to extension and the addition of properties; they are not considered exact types (as mentioned in microsoft/TypeScript#12936) where only known properties are permitted. TypeScript does not fully support exact types, although it treats the types of newly created object literals as such through excess property checks, as you have observed.

If you wish to disallow a specific property key within a type in TypeScript, you can achieve this by making the property optional and assigning its type as never or undefined:

interface IUserSansPassword {
  username: string;
  email: string;
  password?: never; // cannot have a password
}

declare class UserSansPassword implements IUserSansPassword {
  username: string;
  email: string;
  password?: never; // must declare this too
}

Now, UserSansPassword is explicitly without a defined password property. As a result, the following will be flagged as an error:

interface IUser extends IUserSansPassword { // error! 
// Types of property "password" are incompatible
  password: string;
}

You cannot extend IUserSansPassword by adding a password, since if A extends B, an instance of A can always be used in place of a B instance. However, you can extend a related type - your original IUserSansPassword - which can be generated using the Omit helper type:

interface IUser extends Omit<IUserSansPassword, "password"> {
  password: string;
}

declare class User implements IUser {
  username: string;
  email: string;
  password: string;
}

Subsequently, situations like the following will result in an expected error:

const userSansPassword: UserSansPassword = new User();
// error, mismatch on "password" prop

Link to code

Answer №2

In the realm of Object-Oriented Programming languages, if UserWithPassword is a subclass of UserWithoutPassword, then any OOP language permits the assignment. The noteworthy difference in TypeScript is that you do not need to explicitly implement the interface. TypeScript simply verifies that the object's structure aligns with the interface type.

Consider another scenario - when initializing an object literal, excess properties are strictly prohibited. The compiler requires the created object to precisely match the type of the variable it will be assigned to. It would be a misstep to create an object only to lose part of its type immediately after assigning it.

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

Issue with typings in TypeScript is not being resolved

I have integrated this library into my code Library Link I have added typings for it in my project as follows Typings Link I have included it in my .ts file like this import accounting from "accounting"; I can locate the typings under /node_modules ...

Having trouble showing the fa-folders icon in Vuetify?

Utilizing both Vuetify and font-awesome icons has been a successful combination for my project. However, I am facing an issue where the 'fa-folders' icon is not displaying as expected: In the .ts file: import { library } from '@fortawesome/ ...

The disabled attribute in Angular 2+ does not seem to work for a div element when attempting to loop through ngFor

I am currently developing an appointment booking application that showcases time slots for appointments by utilizing the *ngFor loop. html <div *ngFor="let item of allTimeSlots"> <div class="col-sm-3"> <div class="choice" data- ...

Establishing a Next.js API endpoint at the root level

I have a webpage located at URL root, which has been developed using React. Now, I am looking to create an API endpoint on the root as well. `http://localhost:3000/` > directs to the React page `http://localhost:3000/foo` > leads to the Next API end ...

Tips for iterating through nested objects with a for loop

Struggling with validations in an Angular 5 application? If you have a form with name, email, gender, and address grouped under city, state, country using FormGroupname, you might find this code snippet helpful: export class RegistrationComponent implemen ...

tsc is not able to identify virtual fields in a mongoose schema

I'm having trouble getting mongoose virtual to work in typescript. Following the instructions outlined in "another approach" on mongoose documentation, I used mongoose's InferSchemaType to create the interface. However, TSC does not recognize t ...

Instructions for creating a function that can receive an array of objects containing a particular data type for the value associated with the key K

Seeking guidance on how to define a specific signature for a function that accepts an array of objects and 3 column names as input: function customFunction<T, K extends keyof T>( dataset: T[], propertyOne: K, propertyTwo: K, propertyThird: K ...

A recursive function that creates the error message "Using type 'never' as an index type is not allowed."

I found myself wanting to create a recursive private function within a class that would iterate through the nested properties of an object, no matter how many levels deep they go. private loop(item:any) { for(let property in item){ if (typeof ...

What is the process for resetting the mat-date-range-input selection on the calendar?

I've encountered a puzzling problem that has me stumped. I'm working with a mat date range picker in Angular Typescript and have run into an issue while trying to clear any selection made through a function. The code snippet below successfully c ...

Error in Angular: Trying to access the property 'id' of an undefined value

I am facing an issue with a div tag in my HTML file. The code snippet looks like this: <div *ngIf="chat.asReceiver.id != user?.id; else otherParty"> Unfortunately, it always returns the following error: ERROR TypeError: Cannot read propert ...

Oops! An issue occurred during the `ng build` command, indicating a ReferenceError with the message "Buffer is not defined

I'm facing an issue while trying to utilize Buffer in my angular component ts for encoding the Authorization string. Even after attempting npm i @types/node and adding "node" to types field in tsconfig.json, it still doesn't compile with ng buil ...

Sending a document to a server using the Next.js API endpoint

While utilizing the Next.js API as a middleware to handle requests before sending them to the server, I encountered an issue with sending a multipart/formdata request containing a file. The process works fine when directly calling the backend API from the ...

ES6 arrow function fails to recognize the 'this' operator after being transpiled into JavaScript

Currently, I am developing a node application that undergoes transpilation from TypeScript ES6 to JavaScript ES6. To manage class dependencies, I am employing inversify for dependency injection. However, I encountered an error while trying to access member ...

Experimenting with nested dual dynamic routing within the app's directory

Currently working with NextJS 13 and executing the following operations within the app directory. I am attempting to utilize the generateStaticParams function to generate static pages during build time. The route structure is: subpage/[categoryName]/[gif ...

Creating a dynamic calendar application with Angular 2 using PrimeNG components

After diving into PrimeNG, I diligently followed the instructions on their documentation to get everything set up. However, my UI ended up looking quite odd, similar to the image below. Does anyone have any insight as to why this might be happening? Here ...

Determine the field's type without using generic type arguments

In my code, there is a type called Component with a generic parameter named Props, which must adhere to the Record<string, any> structure. I am looking to create a type that can accept a component in one property and also include a function that retu ...

Utilizing the index of the .map function in conjunction with internal methods

After running my code, I encountered the following error message: Warning: Encountered two children with the same key, `classroom-1278238`. Keys are required to be unique so that components can maintain their identity during updates. Having non-unique keys ...

Arrange the columns in Angular Material Table in various directions

Is there a way to sort all columns in an Angular material table by descending order, while keeping the active column sorted in ascending order? I have been trying to achieve this using the code below: @ViewChild(MatSort) sort: MatSort; <table matSort ...

What is the best way to ensure that multiple queries are returned in the correct sequence?

In the interface below, there is a search input box along with data displayed. As you type in the search input, the data below filters accordingly. Each letter typed triggers a request to retrieve the relevant data. For instance, if you type "folder," the ...

How to toggle code block visibility in Angular's ngOnInit() method

I'm currently analyzing different design patterns to optimize the number of REST calls when implementing a 'save while typing feature'. To provide a general overview, in my ngOnInit() function, I have included the following code snippet (wit ...