Home > Back-end >  Typescript Interface Type from Parameters<>
Typescript Interface Type from Parameters<>

Time:10-14

While adding an abstraction layer to a third-party library, I want to pass an object that contains some information the library's function parameters along but cannot get the right conversion.

// Types from Lib
type LibData = any | any[]
type LibOpts = { [key: string]: any }
type ExternalLibFunction = (data: LibData, opts?: LibOpts) => any

// Parameters<ExternalLibraryFunction> = [data: LibData, opts?: LibOpts]
type LibFunctionParameters = Parameters<ExternalLibFunction>

// How to convert LibFunctionParameters to...?
type LibParametersKeyed = {
  data: LibData
  opts?: LibOpts
}

// Use Example
type RunSet = { foo: any } & LibParametersKeyed

The library is xlsx on npm if that helps

CodePudding user response:

Tuple element labels are purely for documentation purposes. Like function parameter names, they have no effect on type compatibility and are completely unobservable in the type system. They cannot be extracted to string literal types. See microsoft/TypeScript#31627 for a declined request to extract function parameter names to string literals.

For example, the following types are completely equivalent:

// tuple labels are unobservable in the type system
declare var x: [data: LibData, opts?: LibOpts];
declare var x: [foo: LibData, bar?: LibOpts];
declare var x: [LibData, LibOpts?];

So there's no way to get "data" and "opts" out of the ExternalLibFunction or LibFunctionParameters types.

If you want to be able to turn LibFunctionParameters into {data: LibData, opts?: LibObpts}, you'll need to supply the ordered list of key names yourself. Meaning the transformation would look like TupleToObject<LibFunctionParameters, ["data", "opts"]> for some suitably-defined TupleToObject type function.

Let's define it.


Given a general tuple type T and another tuple K of the same length whose elements are key names, you can convert the tuple T to an object with keys in K and whose values are the corresponding value from T like this:

type TupleToObject<T extends any[], K extends { [I in keyof T]: PropertyKey }> = {
    [I in keyof T as I extends keyof any[] ? never : K[I]]: T[I]
}

TupleToObject<T> uses key remapping to turn each index I of the T tuple into a key from K (K[I] is the Ith element of the K tuple). Note that I in keyof T as iterates over all the keys in T, including array properties and method keys like "push" or "length" or number (see microsoft/TypeScript#40586 for a request to change this); we don't want to deal with those, so we filter them out by writing I extends keyof any[] ? never : to suppress them.

Let's verify that it works for your example:

type LibParametersKeyed = TupleToObject<LibFunctionParameters, ["data", "opts"]>

/* type LibParametersKeyed = {
    data: LibData;
    opts?: LibOpts | undefined;
} */

Looks good!

Playground link to code

  • Related