5

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

6
  • Whare are LibData and LibOptions? Commented Oct 13, 2021 at 15:54
  • Then what do they have to do with your LibParameters? LibParameters is not used anywhere in your current codeblock Commented Oct 13, 2021 at 15:56
  • 2
    It appears that this may not be possible, at least as of 2019. Check out Get function parameter names and types in TypeScript for a good explanation of why Commented Oct 13, 2021 at 16:02
  • 2
    Generally it's best for example code not to have undeclared types in them; could you fix that so that it's a minimal reproducible example? You can't convert between tuple element labels and string literal types, so there's no way to do it automatically. You can manually write out the mapping from tuple position to key name. This is the reverse question. If you produce a minimal reproducible example there is a way to do it with something like Convert<LibParameters, ["data", "opts"]>; would that be acceptable to you? Commented Oct 13, 2021 at 16:02
  • When you link an answer from @jcalz at the same time that he shows up and posts a comment of his own (⁎⁀⎵⁀⁎) Commented Oct 13, 2021 at 16:03

1 Answer 1

4

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

Sign up to request clarification or add additional context in comments.

1 Comment

This is wonderful, thank you! Didn't get a chance to properly write a note yesterday but wanted to let you know the effort and time to answer is very altruistic and appreciated. Many thanks :) - @jcalz

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.