Reputation: 2073
I have array where each item is array [name: string, someFunction: Function]
. I would like to convert it to object, where keys are name
s and values are someFunction
s:
// Input
const arrayFunctions = [
['getLength', (text: string) => text.length],
['setValue', (id: string, value: number) => {}],
['getAll', () => ([1, 2, 3])]
]
// Output
const objectFunctions = {
getLength: (text: string) => text.length,
setValue: (id: string, value: number) => {},
getAll: () => ([1, 2, 3])
}
Is there any way to connect type of function in input array and type of function in output object?
type ObjectFunctions<ArrayFunctions> = { [/* Value from ArrayFunctions[i][0] */]: /* Value from ArrayFunctions[i][1] */ }
const arrayToObject = <ArrayFunctions extends Array<any>>(functions: ArrayFunctions) => {
const result = {}
for (const [name, func] of functions) {
result[name] = func
}
return result as ObjectFunctions<ArrayFunctions>
}
const arrayFunctions = [
['getLength', (text: string) => text.length],
['setValue', (id: string, value: number) => {}],
['getAll', () => ([1, 2, 3])]
]
const objectFunctions = arrayToObject(arrayFunctions)
const length = objectFunctions.getLength() // Should be error because first parameter (text) is missing.
objectFunctions.setValue(true, 2) // Should be error, because of first parameter (id) must be string.
Upvotes: 7
Views: 8414
Reputation: 476
This same problem has been plaguing me for days. Though, with [email protected]
, this is possible with a custom typedef:
type ArrayToObject<Arr extends any[]> = {
[Entry in keyof Arr as string]: Arr[Entry];
};
And you could use like:
type FuncParamsToObj<Func extends (...args: any[]) => any> = ArrayToObject<Parameters<Func>>;
type myFunc = ( a: string, b?: { x?: number, y?: object } ) => string;
const paramObj: FuncParamsToObj<myFunc> = {
a: 1,
b: { x: 7, y: { w: 'hi' }},
};
Upvotes: -2
Reputation: 37918
It is possible if the array is defined at compile time, so typescript will have a chance to infer the types.
Convert inner tuple to object:
type ToObject<T> = T extends readonly [infer Key, infer Func]
? Key extends PropertyKey
? { [P in Key]: Func } : never : never;
This will allow us to convert ['getLength', (text: string) => text.length]
To { getLength: (text: string) => number }
Convert array of tuples to array of objects (mapped type on array):
type ToObjectsArray<T> = {
[I in keyof T]: ToObject<T[I]>
};
This will allow us to convert array of arrays to array of objects.
We can now extract union of desired objects by querying the type of array item Array[number]
.
The last step - we actually need intersection instead of union. We can use the famous UnionToIntersection:
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
Combine all together:
// @ts-ignore
type FunctionMap<ArrayFunctions> = UnionToIntersection<ToObjectsArray<ArrayFunctions>[number]>;
Ignore above needed because typescript forgets that it produces array when using mapped type on array type.
OK, let's test:
const arrayToObject = <ArrayFunctions extends ReadonlyArray<any>>(functions: ArrayFunctions) => {
const result: any = {}
for (const [name, func] of functions) {
result[name] = func
}
return result as FunctionMap<ArrayFunctions>
}
const arrayFunctions = [
['getLength', (text: string) => text.length],
['setValue', (id: string, value: number) => { }],
['getAll', () => ([1, 2, 3])]
] as const;
const objectFunctions = arrayToObject(arrayFunctions);
const l = objectFunctions.getLength() // Expected 1 arguments, but got 0
objectFunctions.setValue(true, 2) // Argument of type 'true' is not assignable to parameter of type 'string'.
Upvotes: 13
Reputation: 4611
You can't, Typescript compiler cannot guess types dynamically(at runtime).
In typescript, advanced types like ReturnType
or InstanceType
are only allowed to guess already defined types.
Upvotes: 0