Search code examples
typescriptmapped-types

Can I map from a record type to a function type using mapped conditional types?


Given the following type in Typescript

type MyType = {
    foo: int,
    bar: string
}

can I define a mapping (preferriby using mapped conditional types) so that I get

type MyTypeFn = (foo: int, bar: string) => MyType

Solution

  • Not really. There's no way to enumerate the keys of an object in any predictable or usable order. You can do something programmatic, but you have to start with a more easily manipulable type, like this:

    type MySchema = [['foo', number], ['bar', string]]
    

    which is a tuple of 2-tuples representing the key-value pairs in your type. Then you can synthesize MyType using a (TSv2.8 and up) type function such as:

    type MakeMyType<S extends [string, any][], M = S[number]> = 
      { [K in S[number][0]]: M extends [K, any] ? M[1] : never }
    

    which you can verify:

    type MyType = MakeMyType<MySchema>
    // inspects as { foo: number; bar: string }
    

    And then you can synthesize MyTypeFn using a type function such as:

    type MakeMyFunction<S extends [string, any][], L = S['length'], T = MakeMyType<S>> =
      L extends 0 ? () => T :
      L extends 1 ? (a0: S[0][1]) => T :
      L extends 2 ? (a0: S[0][1], a1: S[1][1]) => T :
      L extends 3 ? (a0: S[0][1], a1: S[1][1], a2: S[2][1]) => T :
      L extends 4 ? (a0: S[0][1], a1: S[1][1], a2: S[2][1], a3: S[3][1]) => T :
      L extends 5 ? (a0: S[0][1], a1: S[1][1], a2: S[2][1], a3: S[3][1], a4: S[4][1]) => T :
      L extends 6 ? (a0: S[0][1], a1: S[1][1], a2: S[2][1], a3: S[3][1], a4: S[4][1], a5: S[5][1]) => T :
      (...args: S[number][1][]) => T
    

    Note how there's no programmatic way to do variable-length argument lists, so the best you can do is make cases up to some fixed maximum length as above. Let's verify:

    type MyTypeFn = MakeMyFunction<MySchema>
    // inspects as (a0: number, a1: string) => { foo: number; bar: string; }
    

    And yes, the parameter names are also lost (foo and bar become a0 and a1). This doesn't affect type compatibility of a function signature, where the parameter names are not important. But I'm guessing you want those parameter names for hinting when using or implementing the function. If so, then that's another "not really".

    So that's a long drawn-out way of saying "not really" multiple times. Oh well, Maybe it's been of some help anyway. Good luck!