A banned property names Typscript type

My goal is to create a type that excludes certain literal types and accepts every other string. I initially attempted the following:

type ExcludedKeys = "a"|"b"

type MyType = {
    [K in Exclude<string,ExcludedKeys>]: any
}

const obj: MyType = {
    a: 0, // No Error
    b: 1 // No Error
}

However, I discovered that Exclude<string,ExcludedKeys> just simplifies to string, making this method unfeasible. So, I tried another approach:

type ExcludedKeys = "a"|"b"

type MyType<T> = keyof T extends ExcludedKeys ? never : {
    [K in keyof T]: T[K]
} 

declare class Obj {
    a: number
    b: number
    c: number // Adding this removes the desired error.
}

const obj: MyType<Obj> = {
    a: 0, // No Error
    b: 1, // No Error
    c: 3
}

This method only works effectively when the members of ExcludedKeys are the sole properties of the object.

Playground link

What I'm Looking For

In simple terms, a type that prohibits assigning certain property names based on a set of strings.

type ExcludedKeys = "a"|"b"

const obj = {
    a: 0, // Error Here
    b: 1, // Error Here
    c: 3
}

Edit

While I didn't mention it earlier to keep things concise, I require this type to retain the type information from a specific class model, as highlighted in jsejcksn's answer. Therefore, I have modified the approach suggested by lepsch to better suit my needs.

type ExcludedKeys = "a"|"b"

type MyType<T> = {
    [K in keyof T]: T[K]
} & {
    [K in ExcludedKeys]?: never
}

Playground link

Answer №1

Just one step away from the solution. Give this a try:

type RestrictedKeys = "c"|"d"

type DataType = {
    [key: string]: any
} & {
    [K in RestrictedKeys]: never
}

const data: DataType = {
    c: 3, // No Issue
    d: 2, // No Issue
    e: 1, // Error
}

Answer №2

Before we dive in, let's set the stage:

If you refer to another post, it provides a snippet that enables you to steer clear of assigning values to specific keys through a type annotation. However, this method might sacrifice type details for other properties:

Playground Link

type ExcludedKeys = "a" | "b";

type MyType1 = {
  [key: string]: any
} & {
  [K in ExcludedKeys]: never
}

const obj1: MyType1 = {
  a: 0, // Error
  b: 1, // Error
  c: 3, // No Error
}

obj1.a
   //^? (property) a: never
obj1.b
   //^? (property) b: never
obj1.c
   //^? any (this should be `number`)
obj1.d
   //^? any (oops, this doesn't actually exist)
// ...etc.

If your goal is to shield certain keys and maintain type information for remaining properties, employing a generically constrained identity function proves useful:

Playground Link

type ExcludedKeys = "a" | "b";

function createMyValue<
  T extends Record<string, any> & Partial<Record<ExcludedKeys, never>>,
>(value: T): T {
  return value;
}

createMyValue({
  a: 'hello', /*
  ~
  Type 'string' is not assignable to type 'never'.(2322) */
  b: 1, /*
  ~
  Type 'number' is not assignable to type 'never'.(2322) */
  c: true,
});

const obj = createMyValue({
  c: true,
  d: 'hello',
  e: 2,
});

obj.c
  //^? (property) c: boolean
obj.d
  //^? (property) d: string
obj.e
  //^? (property) e: number
obj.f /*
    ~
Property 'f' does not exist on type '{ c: boolean; d: string; e: number; }'.(2339) */

Answer №3

If you want to filter out certain keys from a type, you can create a utility type that excludes any keys specified in the ExcludedKeys type. Here is an example:

type ExcludedKeys = 'x' | 'y';

type ExcludeKeys<T extends object, ToExclude> = {
  [K in keyof T]: K extends ToExclude ? never : any;
};

class SampleObject {
    x: number;
    y: string;
    z: boolean;
};

const filteredObj: ExcludeKeys<SampleObject, ExcludedKeys> = {
  x: 0, // Type 'number' is not assignable to type 'never'
  y: 'hello', // Type 'string' is not assignable to type 'never'
  z: true, // works fine
};

Answer №4

To incorporate the restricted keys as additional optional attributes of type undefined into the type, follow these steps:

type ExcludedKeys = "a"|"b"


type ExcludeKeysFrom<T extends object, ToExlude extends PropertyKey> = Omit<T, ToExlude> & Partial<Record<ToExlude, undefined>>

declare class Obj  {
    a: number;
    b: number;
    c: number;
};

// error as expected
const obj: ExcludeKeysFrom<Obj, ExcludedKeys> = {
  a: 0, // Type 'number' is not assignable to type 'never'
  b: 0, // Type 'number' is not assignable to type 'never'
  c: 0, // works
};

// ok
const obj2: ExcludeKeysFrom<Obj, ExcludedKeys> = {
  c: 0, // works
};

Playground Link

Why use optional properties? This allows them to be omitted from the object. Why undefined instead of never? Because never can be assigned to any other type, resulting in the forbidden properties being accessible and assignable (example)

Answer №5

Although I didn't explicitly mention it for brevity's sake, as highlighted in smithjones' response, the necessity of this type was to retain type information from a specific class model. That being said, the answer provided by davidbrown remains the chosen one because it fulfills my initial query in a concise manner. However, I would like to discuss how I modified that solution to better align with my requirements.

type FilteredKeys = "x"|"y"

type CustomType<T> = {
    [Key in keyof T]: T[Key]
} & {
    [Key in FilteredKeys]?: never
}

Try it out on Playground

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

Unusual behavior observed in the String.split() function in TypeScript

I have a TypeScript model that looks like this: export class Product { id:number; name:string; brand:Brand; price:number; shippingPrice:number; description:string; photoName:string; productType:ProductType; purchaseCoun ...

Initial call for Angular2 service returns null

Dealing with a parent component that has children routes, my goal is to share a project variable among them. The project value may change, and I need all route pages to reflect this change. Following the guide in the Angular 2 tutorial on achieving this, I ...

What is the importance of having the same data type for the searchElement in the argument for Array.prototype.includes()?

Is there an issue with my settings or is this a feature of TypeScript? Consider the code snippet below: type AllowedChars = 'x' | 'y' | 'z'; const exampleArr: AllowedChars[] = ['x', 'y', 'z']; f ...

Using Angular 6 shortcodes in HTML

Is there a way to save an element in HTML as an alias for repeated use in Angular 6 without using *ngIf directive? For instance, consider the following code snippet: <dumb-comp [name]="(someObservable | async).name" [role]="(someObservable | a ...

Utilizing an Angular Component with Customized Dom Element Modifications

I've developed an Angular component that I intend to use across multiple pages in my project. This component serves as a form box with various configurations such as input fields, buttons, and check boxes. While I want the flexibility to modify the co ...

Tips for updating the text content of an HTML input element during a unit test

I am currently writing unit tests for an Angular application and I am attempting to set the text content of an input element using a unit test written with Jasmine. <input type="text" id="accountid" class="form-control col-sm-3" [(ngModel)]="record.acc ...

Implementing a Map in Typescript that includes a generic type in the value

Here is a code snippet I am working with: class A<T> { constructor(public value: T) {} } const map = new Map(); map.set('a', new A('a')); map.set('b', new A(1)); const a = map.get('a'); const b = map.get(& ...

The 'MY_EVENTS_LOAD' argument is incompatible with the 'TakeableChannel<unknown>' parameter in the yeild takeLatest function

I am encountering a TypeScript error while using Redux and Saga as middleware. The error message is as follows: No overload matches this call. The last overload gave the following error. Argument of type '"MY_EVENTS_LOAD"' is not assignabl ...

Exploring the inner workings of Angular v4.4 and gaining insight into the roles of platformBrowserDynamic and PlatformRef

Recently, I have come into possession of an Angular 4.4 application that utilizes Webpack v3.5 and TypeScript v2.3.3. Struggling to decipher the imported code, I am at a loss understanding its functionality and correctness. To simplify matters for analysis ...

Using LitElement: What is the best way to call functions when the Template is defined as a const?

When the template is defined in a separate file, it's not possible to call a function in the component. However, if the template is defined directly as returning rendered HTML with this.func, it works. How can one call a function when the template is ...

Resolving issues with ESLint error related to Promise executor functions

Two warnings are triggered by the code block below ESLint Warning: Avoid using async function as Promise executor. (no-async-promise-executor) ESLint Warning: Function argument expects void return, but returns a Promise instead. (@typescript-eslint/no-mis ...

What could be causing the vue-property-decorator @Emit to malfunction in my Vue TypeScript file?

I am currently working with Typescript and Vuejs, where I have a child component called child.component.tsx import Vue from 'vue'; import Component from 'vue-class-component'; import { Emit } from 'vue-property-decorator'; ...

Utilizing Typescript for parsing large JSON files

I have encountered an issue while trying to parse/process a large 25 MB JSON file using Typescript. It seems that the code I have written is taking too long (and sometimes even timing out). I am not sure why this is happening or if there is a more efficien ...

Upon upgrading TypeScript from version 3.7.2 to the latest version, which is "4.4.4", an error with code TS2339 surfaced where it stated that the property 'msSaveOrOpenBlob' does not exist on the type 'Navigator'

Previously, I successfully utilized the msSaveOrOpenBlob method. However, after updating TypeScript to the latest version, I encountered multiple errors with two distinct issues. window.navigator.msSaveOrOpenBlob(data, filename); Error TS2322: Type &a ...

Learn how to merge two objects and return the resulting object using TypeScript functions within the Palantir platform

I am looking to generate a pivot table by combining data from two objects using TypeScript functions. My plan is to first join the two objects, create a unified object, and then perform groupBy operations along with aggregate functions like sum and min on ...

Response from Mongoose Populate shows empty results

Within my MongoDB, there exist two collections: Users and Contacts. In my project structure, I have defined two models named User and Contact. Each User references an array of contacts, with each contact containing a property called owner that stores the u ...

Start Transloco in Angular before the application begins

For our Angular project, we have implemented Transloco to handle translations. Within my typescript code, I am using the transloco service in this manner: this.translocoService.translate('foo.bar') I understand that it is crucial to ensure that ...

Establishing a deadline for Firestore's Node.js library

When using the Firestore Node.js library, I often find that some operations, like fetching a document, can take longer than expected, sometimes even several minutes. I am looking to set a timeout or deadline of around 20-30 seconds for these operations. ...

When executing npm run build, the fonts are not displayed properly in Vite

I'm running 'npm run build' to compile my project, and then I use the 'npm run preview' command to view it. However, some images that I've set as background images in my SCSS file are not showing up. The same issue occurs with ...

"Although the NextJS client-side data is present, it seems to be struggling to

I am facing an issue while trying to display fetched data on a blog page. Although the data is successfully retrieved client-side and I can view it using console.log(), it does not appear on the page and the elements remain empty. I am completely puzzled. ...