const string = 'world';
const string2 = 'bar';
const string3 = 'test';
interface example {
hello: 'world';
foo: 'bar';
};
How can I check if a string is a key value in an interface?
So that in the example string
and string2
would pass and string3
would throw an error?
Thanks!
CodePudding user response:
Consider this example:
const string = 'world';
const string2 = 'bar';
const string3 = 'test';
interface example {
hello: 'world';
foo: 'bar';
};
type IsAValue<Obj, Str extends string> = {
[Prop in keyof Obj]: Str extends Obj[Prop] ? Str : never
}[keyof Obj]
type Result1 = IsAValue<example, 'world'> // world
type Result2 = IsAValue<example, 'bar'> // bar
type Result3 = IsAValue<example, 'test'> // never
IsAValue
:
Prop
- represents each key
Obj
- represents an object, in our case it is example
interface.
This utility type iterates through each Obj
(example
) key and check whether Obj[Prop]
extends second argument Str
(string
or string2
). If yes - use Str
as an object value, otherwise - use never
.
This line [keyof Obj]
at the end of utility type obtains a union of all values in the object. If some value mathced Str
we will get Str | never
. Because never
is a bottom type, and never
is assignable to any type, union of Str | never
just returns Str
.
If you want to obtain just boolean from IsAValue
, I mean true
- value exists, false
- not exists. You can just add conditional type, which will check whether results extends never
or not:
const string = 'world';
const string2 = 'bar';
const string3 = 'test';
interface example {
hello: 'world';
foo: 'bar';
};
type IsNever<T> = [T] extends [never] ? true : false
type IsAValue<Obj, Str extends string> = IsNever<{
[Prop in keyof Obj]: Str extends Obj[Prop] ? Str : never
}[keyof Obj]> extends false ? true : false
type Result1 = IsAValue<example, 'world'> // true
type Result2 = IsAValue<example, 'bar'> // true
type Result3 = IsAValue<example, 'test'> // false
If you want to throw an error, see this exmaple:
const string = 'world';
const string2 = 'bar';
const string3 = 'test';
type example = {
hello: 'world';
foo: 'bar';
};
type Values<T> = T[keyof T]
type Assert<Obj extends Record<string, string>, Key extends Values<Obj>> = Obj
type Result1 = Assert<example, 'world'> // true
type Result2 = Assert<example, 'bar'> // true
type Result3 = Assert<example, 'test'> // false
You might have noticed, that I have replaced interface example
with type example
. I made it purposely, because I have applied a constraint to Assert
type. First argument should extend Record
. Record
is indexed type whereas interfaces
in TS are not indexed by the default.
More info you will find here and here