2

Suppose we have:

const array = [a, b, c]  // could be any number of items in here
type T = ???  // how to write this?

such that the result is equivalent to

type T = typeof a & typeof b & typeof c

except T is dynamically created based on looping through array (since array may actually have items other than a, b, c). A solution using interfaces may be acceptable as well.

Ray Zhang
  • 1,411
  • 4
  • 18
  • 36

3 Answers3

3

If the array is typed correctly it will be typed as a union of the element types. Ex:

let a = { aProp: 1 };
let b = { bProp: 1 };
let c = { cProp: 1 };
const array = [a, b, c]  // typed as ({ aProp: number; } | { bProp: number; } | { cProp: number; })[]

Starting from this we can transform the union to an intersection using conditional types (see this answer for an explanation of UnionToIntersection) and use a type query to get the type of an item in the array:

type UnionToIntersection<U> = 
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type T = UnionToIntersection<typeof array[number]>  // { aProp: number; } & { bProp: number; } & { cProp: number; }
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
0

Typescript types only exist at compile time, they will be removed at runtime (making it javascript). As the values of the array only exist at runtime, there is no way to get their type on compile-time.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • the vast majority of types are *inferable* at compile-time `const bob = ['a', 'tuple']; type Bob = typeof bob; const otherBob :Bob;` – Hashbrown Feb 21 '19 at 16:09
0

Okay, so I couldn't get @Titian's answer to work; here's another solution

type Intersect<T extends any[]> = {[key in keyof T[number]] :T[number][key]};

And you can use it like this, say enter image description here See how the IDE knows its properties, and it error's if you try to assign anything else!


Unfortunately it thinks they are optional, you can combat this if you force them all to be real.

type Defined<T> = T extends (undefined|void) ? never : T;
type Intersect<T extends any[]> = {[key in keyof T[number]] :Defined<T[number][key]>};

The issue here is if you have a type that is {optional ?:string}, I think you'll lose that nuance.


I thought that simple &s would solve this issue, but it doesn't (and adds an upper bound to the size of your 'dynamic' array; in this case I could only be bothered copy-pasting for 6-length arrays).

type Intersect<T extends any[]> =
    T[5] extends void ? T[4] extends void ? T[3] extends void ? T[2] extends void ? T[1] extends void ? T[0] extends void ?
    [] : T[0] : T[0]&T[1] : T[0]&T[1]&T[2] : T[0]&T[1]&T[2]&T[3] : T[0]&T[1]&T[2]&T[3]&T[4] : T[0]&T[1]&T[2]&T[3]&T[4]&T[5]
;

I feel like the real solution will be when they figure out recursive types.

type Head<T> = T extends [infer U, ...Array<unknown>] ? U : never;
type Tail<T> = T extends any[] ?
    (...args :T) => any extends (head :any, ...args :infer U) => any ?
        U :
        never
    :
    never
;
type Intersect<T extends any[]> = T[1] extends void ? T[0] : Head<T>&Intersect<Tail<T>>;

Head & Tail (which can pull the first type of an array, and the remaining types as a new array type respectively) both work, but Intersect breaks when it refers back to Intersect.

Hashbrown
  • 12,091
  • 8
  • 72
  • 95