1

Given these two types

type keys = ['name', 'age', 'height']
type valueTypes = [string, number, number]

How can I create a zipped object type? {name: string, age: number, height: number}?

Here's my attempt:

type Zip<K, V> = {
    [KK in keyof K]: {
        [VV in keyof V]: K[KK] extends string ? Record<K[KK], V[VV]> : never
    }
}
ThomasReggi
  • 55,053
  • 85
  • 237
  • 424

2 Answers2

7

Normally the term "zip" refers to a convolution, where you turn an ordered pair (or triplet, etc) of arrays into an array of ordered pairs (or triplets, etc). See Python's zip(), Haskell's zip for examples.

One way to zip tuple types in TypeScript, neglecting the edge cases where the tuples are of differing lengths, is as follows:

type ZipTuple<T extends readonly any[], U extends readonly any[]> = {
  [K in keyof T]: [T[K], K extends keyof U ? U[K] : never]
}

We're just using the mapped tuple/array feature added in TS3.1.

That produces the following:

type Keys = ['name', 'age', 'height']
type Values = [string, number, number]

type KVTuple = ZipTuple<Keys, Values>;
// type KVTuple = [["name", string], ["age", number], ["height", number]]

Armed with that, we can define KeyValTuplesToObject<K, V> which takes a tuple K of keys and a same-length tuple V of values and produce the object type where each key maps to the corresponding value:

type KeyValTuplesToObject<K extends readonly PropertyKey[], V extends readonly any[]> =
  ZipTuple<K, V>[number] extends infer Z ?
  [Z] extends [[any, any]] ? { [P in Z[0]]: Extract<Z, [P, any]>[1] } : never : never

That first uses ZipTuple to convert the keys and value tuples to a tuple of key-value pairs, indexes into it with [number] to get the key-value tuples as a union, and then makes a mapped type that iterates over all the keys in these tuples and extracts the value from the corresponding element.


UPDATE for TS4.1; the above KeyValTuplesToObject can be simiplified using key remapping in mapped types:

type KeyValTuplesToObject<K extends readonly PropertyKey[], V extends readonly any[]> =
  { [T in ZipTuple<K, V>[number]as T[0]]: T[1] };

Using the same Keys and Values from before, this is the outcome:

type KVObject = KeyValTuplesToObject<Keys, Values>
/* type KVObject = {
    name: string;
    age: number;
    height: number;
} */

Looks good to me. Hope that helps; good luck!

Link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • Instead of iterating over `keyof T` then test if the key is in `keyof U` we can just iterate over `keyof T & keyof U` like this: `[K in keyof T & keyof U]: [T[K], U[K]]` – aret Apr 03 '22 at 23:43
1

type Shift<T extends any[]> = ((...args: T) => any) extends ((first: any, ...rest: infer R) => any) ? R : never;

type ConvertIntoObject<Keys extends any[], Values extends any[]> = _ConvertIntoObject<Keys, Values, {}>
type _ConvertIntoObject<Keys extends any[], Values extends any[], Acc extends {}> = {
  0: Acc,
  1: _ConvertIntoObject<Shift<Keys>, Shift<Values>, {
    [K in Keys[0]]: Values[0]
  } & Acc>
}[Keys['length'] extends 0 ? 0 : 1];



type a = ["e", "i", "o"]
type b = [1, 2, 3]
type c = ConvertIntoObject<a, b> // type c is { e: 1, i: 2, o: 3}
ThomasReggi
  • 55,053
  • 85
  • 237
  • 424
  • [Circular conditional types](https://github.com/microsoft/TypeScript/issues/26980) of the form you are using are [not currently supported](https://github.com/microsoft/TypeScript/issues/26980#issuecomment-421354117). Luckily they are not necessary to answer this question. Also, please consider adding at least a short explanation. – jcalz Nov 19 '19 at 17:15