9

is there any way to transform named/labeled tuple

type Tup = [a: string, b: number];

to object

type Obj = {
     a: string;
     b: number;
}

last attempt does not wotrk

type Map1<Container extends unknown[]> = {
     [K in keyof Container]: Container[K];
};

type Map2<Container extends Record<PropertyKey, any>, Replace> = {
     [K in keyof Container]: Replace;
};

let c1 : Map1<Tup> = {
     'a' : 's',
     'b' : 1,
};

let c2 : Map2<Tup> = {
     'a' : 's',
     'b' : 1,
};

thanks

Andikac
  • 384
  • 2
  • 13
  • 2
    https://github.com/microsoft/TypeScript/issues/41594 – Roberto Zvjerković Oct 23 '21 at 10:31
  • 1
    @Andikac it is impossible to do. Labels are only for readability/debugging. It is possible to reduce your tuple to object representation, but the output will be `{0:string, 1: number}`. See this answer https://stackoverflow.com/questions/69299590/transform-array-of-objects-to-single-object-and-keep-types-in-typescript/69300965#69300965 – captain-yossarian from Ukraine Oct 23 '21 at 12:53
  • 1
    Given that the type `[a: string, b: number]` and `[c: string, d: number]` are the same type, there's no principled way to convert that single type into `{a: string, b: number}` instead of `{c: string, d: number}`. Those labels just aren't observably part of the type. If you're willing to pass in a tuple of names like `RemapTupleKeys`, then there's a way to do it ([this code](https://tsplay.dev/mZaYKN) for example). Let me know if you want that written up as an answer. – jcalz Oct 24 '21 at 01:54
  • @captain-yossarian i need for "overload like" implementation function FnParam(a: string, b: number) {} function FnDestructure({a, b} : TupleToObject>) {}, reducing to {0:string, 1: number} wont work – Andikac Oct 25 '21 at 03:59
  • @jcalz yes please write up as an answer, a good alternative while waiting for this feature in Roberto Zvjerković comment – Andikac Oct 25 '21 at 04:05
  • I will write up an answer, but https://github.com/microsoft/TypeScript/issues/41594 is specifically asking for an IDE-based *refactoring* which turns a labeled tuple type into an object type. It wouldn't be available as a type level operation, and I think there is probably never going to be a way to extract tuple labels or function parameter names into string literal types (although I could imagine the reverse happening) – jcalz Oct 25 '21 at 18:36

1 Answers1

7

TypeScript's support for labeled tuple elements as implemented in microsoft/TypeScript#38234 intentionally does not provide any way to observe these labels in the type system. From the implementing pull request:

Names do not affect assignability in any way, and just exist for documentation and quickinfo.

Also see this comment in a related issue where the dev lead for the TS team says:

[Tuple labels] are purely for display purposes (similar to parameter names in function types)

The tuple type [a: string, b: number] and the tuple type [c: string, d: number] are indistinguishable from the point of view of the type system. The only difference between them is how they are displayed in an IDE or output as documentation. They are more like comments than code.

If [a: string, b: number] and [c: string, d: number] are the same type, then any type function TupleToObject<T> that turns [a: string, b: number] into {a: string, b: number} would also need to turn [c: string, d: number] into {a: string, b: number}.

So there's really no hope of implementing what you're looking for. Tuple labels are intentionally hidden from the type system. There's just no connection between tuple labels and string literal types.


Instead, the only possible way forward is for you to manually specify the string literal keys you'd like to use for each element of the input tuple. So the unworkable TupleToObject<[a: string, b: number]> becomes something like TupleToObject<[a: string, b: number], ["a", "b"]>. You can see how this doesn't care about tuple labels anymore; TupleToObject<[foo: string, bar: number], ["a", "b"]> would by necessity produce the same type.

Okay, so how would someone implement TupleToObject? Here's one potential way:

type TupleToObject<T extends readonly any[],
  M extends Record<Exclude<keyof T, keyof any[]>, PropertyKey>> =
  { [K in Exclude<keyof T, keyof any[]> as M[K]]: T[K] };

It takes two type parameters: T is a meant to be the tuple type, while M is meant to be a tuple type of at least the same length whose values are keys. The extends readonly any[] and extends Record<Exclude<keyof T, keyof any[]>, PropertyKey>> generic constraints more or less enforce this.

Then for each literal numeric-like key of the T tuple, we use key remapping to replace that key with the corresponding entry from M. The only weird bit is that instead of iterating over keyof T, we use Exclude<keyof T, keyof any[]>. Tuple types are arrays, and will contain array method names like "map". We don't want to worry about "map", so the Exclude<T, U> utility type lets us say "all those keys from T which are not general array keys". For tuples this tends to leave just "0", "1", etc.


Let's test it out:

type Tup = [a: string, b: number];

type Obj = TupleToObject<Tup, ["a", "b"]>
/* type Obj = {
    a: string;
    b: number;
} */

Looks good!

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360