Typescript types can inadvertently include unnecessary properties

It seems that when a class is used as a type, only keys of that class should be allowed. However, assigning [Symbol()], [String()], or [Number()] as property keys does not result in an error, allowing incorrect properties to be assigned. An even more curious case is that if [Symbol()] is assigned to a variable before being used as a property key, it causes an error as expected. This is different from the behavior seen when assigning [String()] or [Number()] to a variable first.

const sym = Symbol()
const str = String('another unwanted prop')
const num = Number(1)

class A { // <-- can be class/type/interface
    a?: string
    b?: number
}

let a: A = { 
    [Symbol()]: 'anything', 
    [String('unwanted prop')]: 'anything', 
    [Number(0)]: 'anything', 
    [str]: 'anything',
    [num]: 'anything',
    [sym]: 'anything' // <-- why does this error while [Symbol()], [String()] and [Number()] don't?
//  ~~~~~~~~~~~~~~~~~
}

Playground Link

This behavior does not match my expectations and I find it somewhat confusing.

Is this considered an issue or is it the intended behavior? Am I overlooking something?

Answer №1

It seems that TypeScript is missing a key feature here; computed property names do not usually trigger excess property warnings, except when the computed property name is a literal type (such as "abc") or a unique symbol type (like a named declaration const x = Symbol()).

This issue has been acknowledged by the TypeScript community and reported in microsoft/TypeScript#22427. Although marked as fixed, it remains unresolved as seen in microsoft/TypeScript#36920.

Excess property warning primarily serves as a linter tool rather than a strict type safety mechanism. The object types in TypeScript are open for extension, allowing excess properties without compromising type integrity. Refer to this discussion on Stack Overflow for more insights.

The purpose of excess property warnings is to alert developers about potentially discarding valuable information unintentionally. For instance, when defining a variable as const foo: {x: string} = {x: "hello", y: 123}, the compiler highlights the excess property y as a precaution against potential data loss.

In cases where the compiler could issue warnings but does not, users often submit suggestions and feature requests. These omissions are viewed as inconveniences rather than critical vulnerabilities in type safety. One such example is the request for excess property warnings on non-discriminated union types in microsoft/TypeScript#20863.


Explore the code further in the TypeScript playground using this Playground link.

Answer №2

I managed to solve the issue by utilizing an interface that restricts symbol types:

type Properties<T> = { [Key in keyof T]: T[Key] }

interface NoSymbolRestriction{
    [key: symbol]: never;
}
interface Entity extends NoSymbolRestriction{
    a?: string
    b?: number
}

let entityProperties: Properties<Entity> = { a: '', b: 0, [Symbol()]: 0 } //Results in an Error

If you need to use the Class construct instead of Interface, consider combining them like this:

let entityProperties: Properties<Entity&NoSymbolRestriction> = { a: '', b: 0, [Symbol()]: 0 } //Results in an Error

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

TypeScript - Converting into individual compiled files

Currently, I am working on a project that consists of lengthy source files. While this is advantageous for imports, it poses challenges in terms of maintenance. For instance: /main/core.ts export type Foo { ... } export interface Bar { ... } export cla ...

Struggling to navigate through rows in a Material UI Table

After performing a search in my TextField, the rows appear correctly in the console. However, the table itself does not update at all. I attempted to set the result of the search to a new array, but this made my TextField read-only. Any assistance with fur ...

Checking nested arrays recursively in Typescript

I'm facing difficulty in traversing through a nested array which may contain arrays of itself, representing a dynamic menu structure. Below is how the objects are structured: This is the Interface IMenuNode: Interface IMenuNode: export interface IM ...

What is the best way to send information to a child component that has been navigated from a parent component

When navigating to a child component from the parent component's HTML template using a button, how can I pass the parent component's data (such as a name) to the child component without displaying it in the URL? ...

Build for Node.js using TypeScript with full functionality

When compiling TypeScript for Node.js using tsc --module commonjs --target ES5 I face limitations with async/await and generators as tsc doesn't know how to handle them in ES5. To address this issue, I can compile TypeScript for Node.js with tsc - ...

Is there any advice for resolving the issue "In order to execute the serve command, you must be in an Angular project, but the project definition cannot be located"?

Are there any suggestions for resolving the error message "The serve command requires to be run in an Angular project, but a project definition could not be found."? PS C:\angular\pro\toitsu-view> ng serve The serve command requires to be ...

Tips for selecting specific types from a list using generic types in TypeScript

Can anyone assist me in creating a function that retrieves all instances of a specified type from a list of candidates, each of which is derived from a shared parent class? For example, I attempted the following code: class A { p ...

Is Typescript pass by value or pass by reference?

I have these files: data.ts: export const myData { info1: "info1", info2: "info2", ... ... } and I also have this class: my-class.ts export class MyClass { private data: any; constructor(data: any) { this.data = data ...

angular table cell with a show more/less button

I'm trying to create a button that can hide/unhide text in a table cell if the length is greater than a certain number. However, the current implementation is not working as expected. The button ends up opening all texts in every cell, and it only wor ...

Send the template to the enclosed grid column

I enclosed a kendo-grid-column within a new component by using <imx-gridColumn field="Count" title="Count"> ... </imx-gridColumn> The component that includes the imx-gridColumn is templated with <kendo-grid-column #column field="{{field}} ...

Sending a parameter to a route guard

I've been developing an application that involves multiple roles, each requiring its own guard to restrict access to various parts of the app. While I know it's possible to create separate guard classes for each role, I'm hoping to find a mo ...

When working with Angular 12, the target environment lacks support for dynamic import() syntax. Therefore, utilizing external type 'module' within a script is not feasible

My current issue involves using dynamic import code to bring in a js library during runtime: export class AuthService { constructor() { import('https://apis.google.com/js/platform.js').then(result => { console.log(resul ...

TypeScript's conditional property failing to recognize incorrect functional argument

The concept of a conditional type should encompass smart properties, and I sought assistance from @jcalz in the previous iteration of this query. Even though that issue was resolved, I am still unable to achieve the level of typing strictness I desire. The ...

Why does React redirect me to the main page after refreshing the page, even though the user is authenticated in a private route?

In the process of developing a private route component that restricts unauthenticated users and redirects them to the homepage, we encountered an issue upon page refresh. The problem arises when the current user variable momentarily becomes null after a ...

Deactivate multiple textareas within a loop by utilizing JQuery/Typescript and KnockoutJS

I am working on a for loop that generates a series of textareas with unique ids using KO data-binding, but they all share the same name. My goal is to utilize Jquery to determine if all the textareas in the list are empty. If they are, I want to disable al ...

Issue with Jest mock function failing to trigger axios instance function causing it to return undefined

I initially found a solution on this StackOverflow thread However, I wanted to add my own helper function that generates a fresh Axios instance with the user's authentication token. Here is what I came up with: import axios from "axios"; c ...

What is the reason behind the occurrence of `(User & { _id: Schema.Types.ObjectId; }) | null` when calling findById?

Today marks my debut using typescript and mongoose. Here's a glimpse of what I've worked on. Type export interface User extends Document { _id: ObjectId; lastName: string; } Schema const userSchema = new Schema<User>({ lastName: { t ...

Can you provide the syntax for a generic type parameter placed in front of a function type declaration?

While reviewing a project code recently, I came across some declarations in TypeScript that were unfamiliar to me: export interface SomeInterface<T> { <R>(paths: string[]): Observable<R>; <R>(Fn: (state: T) => R): Observable ...

Error in NodeJS when trying to run ESM: "ReferenceError: exports is not defined

Having a tough time with this task. I'm attempting to execute ESM scripts on Node 14 (AWS Lambda) I am working on running this piece of code to convert 3D objects to THREE JSON. This involves using the command node -r esm fbx2three.js model.fbx. I ...

Having a problem where the Next.js project is functioning in development mode, but encountering a "module not found" error

After following multiple tutorials to integrate Typescript into my existing app, I finally got it running smoothly in dev mode using cross-env NODE_ENV=development ts-node-script ./server/index.js However, when I execute next build, it completes successfu ...