Generic Case
In a function
const myFunction = <T, K extends keyof T>(obj: T, key: K): SpecificType => {
const x: SpecificType = obj[key] // ❌ Type 'T[string]' is not assignable to type SpecificType
}
Is it possible to define myFunction(obj, key)
in such a way I can grant that obj[key] extends SpecificType
?
Example
Given the following function:
type KeysMatching<T, V> = {
[K in keyof T]-?: T[K] extends V ? K : never
}[keyof T]
export const groupArrayBy = <
T extends Record<string, unknown>,
K extends KeysMatching<T, string | number | symbol>
>(
array: T[],
key: K
) => {
return array.reduce((acc, item) => {
const group = item[key]
if (!acc[group]) {
acc[group] = []
}
acc[group].push(item)
return acc
}, {} as Record<T[K], T[]>) // ❌ Type 'T[string]' is not assignable to type 'string | number | symbol'
}
Typescript complains about defining {} as Record<T[K], T[]>
because T[K]
is unknown
(as given by T extends Record<string, unknown>
). But, in reality, it is not, T[K]
should only be string | number | symbol
, because K extends KeysMatching<T, string | number | symbol>
(This auxiliary type was extracted from this question)
In fact, if I call
const t = [
{
valid: 'string',
alsoValid: 1,
group: 2,
invalid: [],
outroInvalid: {}
}
]
groupArrayBy(t, 'valid') // ✅ everything ok
groupArrayBy(t, 'invalid') // ❌ not ok, as expected
And, even so, typescript cannot tell that, in any valid call to groupArrayBy([obj], key)
, obj[key]
will always be string | number | symbol
, which should be compatible with Record<T[K], T[]>