I have this record:
interface TheRecord extends TheRecordType {
a: { typeA: 'string' },
b: { typeB: 123 },
c: { typeA: 'string' },
}
type TheRecordType = Record<string, TypeA | TypeB>
type TypeA = { typeA: string }
type TypeB = { typeB: number }
I want my function to accept only keys who's values are of typeA
doStuff('b'); //this should fail
function doStuff(arg: keyof FilteredForTypeA): void {
...
}
Here's how I try to filter them out
type FilteredForTypeA = { [k in keyof TheRecord]: TheRecord[k] extends TypeA ? TheRecord[k] : never }
There's a few things going on here so I'll make an answer since it's not a direct duplicate of the relevant existing questions I found.
When your type has an index signature it's hard to extract just the "known" literal keys of the object if they are subtypes of the index signature. That is, keyof {[k: string]: any, foo: any}
is just string
, and "foo"
is completely subsumed in that. You can use a conditional type trick to extract just the known literal keys, as shown in this related question:
type RemoveIndex<T> = {
[K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]
};
type KnownKeys<T> = keyof RemoveIndex<T>;
On the other hand, you want only the keys whose values have a property matching a particular type. That is doable with a mapped-conditional-lookup, as shown in this related question:
type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];
Put those together and you get:
type KnownKeysMatching<T, V> = KeysMatching<Pick<T, KnownKeys<T>>, V>
And you can verify that it works as I think you intend:
function doStuff(arg: KnownKeysMatching<TheRecord, TypeA>): void {
}
doStuff('a'); // okay
doStuff('b'); // error!
doStuff('c'); // okay
doStuff('d'); // error!
Note how arg
cannot be 'b'
, as desired, but it also cannot be 'd'
or any other "unknown" string, even though TheRecord
has a string index signature. If you need some other behavior for 'd'
, that could be done, but it seems outside the scope of the question.