I need to create a type which excludes certain literal types and accepts every other string. I tried this:
type ExcludedKeys = "a"|"b"
type MyType = {
[K in Exclude<string,ExcludedKeys>]: any
}
const obj: MyType = {
a: 0, // No Error
b: 1 // No Error
}
But soon I found out that Exclude<string,ExcludedKeys>
simply evaluates to string
and it's not possible to do it this way. Then I have tried this 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 wanted error.
}
const obj: MyType<Obj> = {
a: 0, // No Error
b: 1, // No Error
c: 3
}
but this only works when members of ExcludedKeys
are the only props of the object.
What I need
As said, a type that negates those property names assignable to a set of string
type ExcludedKeys = "a"|"b"
const obj = {
a: 0, // Error Here
b: 1, // Error Here
c: 3
}
Edit
Even though I didn't mention it to simplify the context, as jsejcksn's answer pointed out, I needed this type to preserve type info from a given class model. With that said, lepsch's answer remains the accepted one because it does just what I asked for in the most short and simple way. Anyway, I'd like to share how I changed that approach to suit my needs.
type ExcludedKeys = "a"|"b"
type MyType<T> = {
[K in keyof T]: T[K]
} & {
[K in ExcludedKeys]?: never
}
CodePudding user response:
You're almost there. Try this:
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
}
CodePudding user response:
First, some context:
The code in another answer will help you avoid assigning values to the excluded keys using only a type annotation, but you'll lose type information for the other properties:
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.
To prevent the excluded keys and preserve type information for the other properties of your value, you can use a generically-constrained identity function:
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) */
CodePudding user response:
You can map the keys of a type and check if a key extends
your ExcludedKeys
. Here's a utility type for this:
type ExcludedKeys = 'a' | 'b';
type ExcludeKeys<T extends object, ToExlude> = {
[K in keyof T]: K extends ToExlude ? never : any;
};
declare class Obj {
a: number;
b: number;
c: number;
};
const obj: ExcludeKeys<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
};
CodePudding user response:
You can add the forbidden keys as extra optional properties of type undefined
to the type:
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;
};
// erro 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
};
Why optional? So that they don't need to present in the object.
Why undefined
and not never
because never
is assignable to any other type so if you were to use never
the resulting object gains all the forbidden properties which you can access and which will be assignable to anything (ex)
CodePudding user response:
Even though I didn't mention it to simplify the context, as jsejcksn's answer pointed out, I needed this type to preserve type info from a given class model. With that said, lepsch's answer remains the accepted one because it does just what I asked for in the most short and simple way. Anyway, I'd like to share how I changed that approach to suit my needs.
type ExcludedKeys = "a"|"b"
type MyType<T> = {
[K in keyof T]: T[K]
} & {
[K in ExcludedKeys]?: never
}