Home > Back-end >  Type that transform entries values?
Type that transform entries values?

Time:12-16

I would like to define a type like:

declare type TheOneMillionDollarType<
    Entry extends [string, any], 
    TrMap extends [Entry[1], any]
> = /* TODO */unknown;

Such that here:

type Input = [ "foo", string ] | [ "bar", number ];

type TrMap = [ string, "string !" ] | [ number, "number !" ];

type Expected = [ "foo", "string !" ] | [ "bar", "number !" ];

type Got = TheOneMillionDollarType<Input, TrMap>;

Expected and Got would be the same type.

Very concretely. I would like the code of this playground to compile.

CodePudding user response:

One approach is to define it like this:

type TheOneMillionDollarType<
  E extends [string, any],
  M extends [E[1], any]
  > = E extends unknown ? [E[0], Extract<M, [E[1], any]>[1]] : never;

The type E extends unknown ? ... : never looks like a no-op, but it's actually a distributive conditional type that splits E into its union members, evaluates the ... part for each one, and then unions (unites? unionizes? unifies? whatever) the results back together at the end.

That type is [E[0], Extract<M, [E[1], any]>[1]], a tuple type like you want where the first element is the same as the first element of E. The second element uses the Extract<T, U> utility type to filter the M union and find any member(s) assignable to [E[1], any]. That means we are selecting any member from M where the first element is assignable to the second element of E. Once we get that, we then index into it ([1]) to get its second element.

And that works for your example at least:

type Got = TheOneMillionDollarType<Input, TrMap>;
// type Got = ["foo", "string !"] | ["bar", "number !"]

Note that there are likely a lot of edge cases:

type Hmm = TheOneMillionDollarType<["foo", string | number], [string, "a"]>
// type Hmm = ["foo", "a"]
type AlsoHmm = TheOneMillionDollarType<["foo", number], [0, "z"] | [1, "y"]>
// type AlsoHmm = ["foo", "z" | "y"]
type ThirdHmm = TheOneMillionDollarType<["foo", string] | ["bar", number], [string | number, "a"]>;
// type ThirdHmm = ["foo", never] | ["bar", never]

Bet that third one isn't what you'd want, but maybe you don't care about that edge case. Anyway I recommend you test thoroughly, in case it turns out to be TheThreeDollarFiftyCentType for your actual use cases.

Playground link to code

  • Related