Search code examples
typescriptmapped-types

typescript generate index signature type from 2 union types


given

type key = "a" | "b" | "c"
type value = 1 | 2 | 3

I want to generate {a:1, b:2, c:3} using two union types in above

type A<K,V> = ?????

what is ?????


Solution

  • Technically, it is possible to do. COnsider this example:

    type key = "a" | "b" | "c"
    type value = 1 | 2 | 3
    
    // credits goes to https://stackoverflow.com/a/50375286
    type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
      k: infer I
    ) => void
      ? I
      : never;
    
    // credits goes to https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468114901
    type UnionToOvlds<U> = UnionToIntersection<
      U extends any ? (f: U) => void : never
    >;
    
    type PopUnion<U> = UnionToOvlds<U> extends (a: infer A) => void ? A : never;
    
    // credits goes to https://stackoverflow.com/questions/53953814/typescript-check-if-a-type-is-a-union#comment-94748994
    type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
    
    type UnionToArray<T, A extends unknown[] = []> = IsUnion<T> extends true
      ? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]>
      : [T, ...A];
    
    type Length<T extends ReadonlyArray<any>> = T extends { length: infer L }
      ? L
      : never;
    
    type CompareLength<
      X extends ReadonlyArray<any>,
      Y extends ReadonlyArray<any>
      > = Length<X> extends Length<Y> ? true : false;
    
    
    type Callback<T, U> = T extends PropertyKey ? U extends PropertyKey ? Record<T, U> : never : never
    
    // https://catchts.com/tuples#zip 
    type Zip<
      T extends ReadonlyArray<any>,
      U extends ReadonlyArray<any>,
      Result extends Record<string, any> = {}> =
      (CompareLength<T, U> extends true
        ? (T extends []
          ? Result
          : (T extends [infer HeadT1]
            ? (U extends [infer HeadU1]
              ? Result & Callback<HeadT1, HeadU1>
              : never)
            : (T extends [infer HeadT2, ...infer TailT2]
              ? (U extends [infer HeadU2, ...infer TailU2]
                ? Zip<TailT2, TailU2, Result & Callback<HeadT2, HeadU2>>
                : never)
              : never
            )
          )
        )
        : never);
    
    
    
    type Keys = UnionToArray<key>;
    
    type Values = UnionToArray<value>
    
    // Record<"a", 1> & Record<"b", 2> & Record<"c", 3>
    type Result = Zip<Keys, Values>
    

    Playground

    I have converted both key and value to tuples and then just merged them.

    However, you should not do this in your production code. It is UNSAFE. Please see this issue/17944 and my question.

    You should never rely on union order as well as Object.keys order.