I want to create a function with array of literal string from available key.
If first index using SECOND_KEY, in that index it must return that SECOND_TYPE type, and so on.
The code is below, and I provide comments to elaborate more:
type TestType = { FIRST_KEY: number, SECOND_KEY: string } function TestFunction<keyType extends (keyof TestType)[]>(keys: keyType) { return await AsyncStorage.multiGet(keys) /* from multiGet, probably the data will be like this [ 2, "Test string", 2 ] */ } let arr = TestFunction(['FIRST_KEY', 'SECOND_KEY', 'FIRST_KEY']) // arr[0] should have type number, and be able to access number methods like .toPrecision // arr[1] should have type string, and be able to access string methods like .length // arr[2] should have type number, and be able to access number methods like .toPrecision
Answer
By default Typescript will interpret your generic type parameter as a normal array ("FIRST_KEY" | "SECOND_KEY")[]
which can have any length and can have these values in any order.
We need to force Typescript to interpret it as a fixed tuple type. We can do that by adding as const
when we call the function, and by adding readonly
to the generic in order to allow constant tuples which are readonly
.
We need a mapped type to convert our tuple of keys to a tuple of values. Mapped types can be applied directly to tuples.
Within the body of the function, you will need to assert correctness with as
because calling methods like .map
on your function will cause it to be seen as normal array again.
Your actual use case regards AsyncStorage
/localStorage
where the value that you get will always be a string
. You will need to convert it to a number
with parseFloat
in certain cases. This is a run-time action and cannot be done just with types. Your code needs to know which variables it should convert. Here we only have two keys, so we can deal with it on an if
/then
basis, but you might need to rethink how you handle casting in your actual app.
type TestType = { FIRST_KEY: number, SECOND_KEY: string } type TestValue<T> = { [K in keyof T]: T[K] extends keyof TestType ? TestType[T[K]] : never; } function TestFunction<keyType extends readonly (keyof TestType)[]>(keys: keyType): TestValue<keyType> { return keys.map(key => { const raw = localStorage.getItem(key); // is either string or null if (key === "FIRST_KEY") { return parseFloat(raw ?? ""); } else { return raw ?? ""; } }) as unknown as TestValue<keyType>; } let arr = TestFunction(['FIRST_KEY', 'SECOND_KEY', 'FIRST_KEY'] as const) const [a, b, c] = arr; // types a: number, b: string, c: number