'Keyof not working in template literals due to Symbols
I would expect either of these types to work, but these both throw errors.
export type Object1<T extends { [k: string]: any }> = `${keyof T}`;
export type Object2<T extends Record<string, any>> = `${keyof T}`;
They both give this error
TS2322: Type 'key T' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
Type 'string | number | symbol' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
I'm not understanding why symbol | number are appearing despite specifying that the keys are strings.
Solution 1:[1]
The issue is that the constraints don't restrict the keys to strings. They require that all string keys in T have a value of type any, but it is perfectly fine for T to have other keys as well.
For example, if you leave out the template literal type to get rid of the error, you can apply Object1 to an object type with a string, a number, and a symbol key:
type Object1<T extends {[k: string]: any}> = keyof T
type Test1 = Object1<{s: 'str', 1: 'num', [Symbol.iterator]: 'sym'}>
// type Test1 = typeof Symbol.iterator | "s" | 1
And the same thing holds for Object2:
type Object2<T extends Record<string, any>> = keyof T
type Test2 = Object2<{s: 'str', 1: 'num', [Symbol.iterator]: 'sym'}>
// type Test1 = typeof Symbol.iterator | "s" | 1
Solution 2:[2]
The answer of @Oblosys offers a good explanation why keyof still produces all possible key types even though you specified a constraint. If I understand your question correctly, your goal is to get all the keys of a type inside a string literal (maybe for further string manipulation?)
Maybe this can help you:
export type KeyLiterals<T extends {[k: string] : any}> =
`${keyof T extends string | number ? keyof T : never}`;
type Test = KeyLiterals<{a: string, b: string}> // 'a' | 'b'
We can check if keyof is of type string or number to make sure that no symbols remain in the union.
Now you can do any further string manipulation like adding a prefix.
export type KeyLiterals2<T extends {[k: string] : any}> =
`prefix_${keyof T extends string | number ? keyof T : never}`;
type Test2 = KeyLiterals2<{a: string, b: string}> // 'prefix_a' | 'prefix_b'
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Oblosys |
| Solution 2 | Tobias S. |
