Michal
Michal

Reputation: 2073

TypeScript types from array to object

I have array where each item is array [name: string, someFunction: Function]. I would like to convert it to object, where keys are names and values are someFunctions:

// 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

Answers (3)

yuyu5
yuyu5

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

Aleksey L.
Aleksey L.

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'.

Playground

Upvotes: 13

MJ Studio
MJ Studio

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

Related Questions