Home > Back-end >  Combine all types in array into a single type
Combine all types in array into a single type

Time:03-24

I have a superObject() function which just combined a lot of objects:

type Class<T = any> = {
    new(...args: any[]): T
}

function superObject<C extends Class[]>(...classes: C) {
    let obj = {};
    for(const klass of classes) {
        obj = {
            ...obj,
            ...new klass()
        }
    }
    return obj;
}

There does not seem to be a good way to annotate the return type, as obj will have every property of every class. I have tried marking the function as returning C[number], but this gives a union (classes[0] | classes[1], not classes[0] & classes[1]). How would I get this type at compile time?

CodePudding user response:

If you define a recursive type:

type MergeTypes<T extends unknown[]> = 
    T extends [a: infer A, ...rest: infer R] ? A & MergeTypes<R> : {};

to merge all of the types in a tuple T, then you can:

type Class<T = any> = {
  new(...args: any[]): T
}

function superObject<C extends Class[]>(...classes: C): MergeTypes<C> {
  let obj: Partial<MergeTypes<C>> = {};
  for (const klass of classes) {
    obj = {
      ...obj,
      ...new klass()
    }
  }
  return obj as MergeTypes<C>;
}

REVISION

Upon using the code above, I realize that it's merging the constructor function types, not the types that are constructed. Here's a revised version that extracts the T from Class<T> and uses that instead.

type Class<T = any> = {
  new(...args: any[]): T
}

type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;

type MergeTypes<T extends Class[]> =
  T extends [a: Class<infer A>, ...rest: infer R]
  ? R extends Class[]
  ? A & MergeTypes<R>
  : never
  : {};

function superObject<C extends Class[]>(...classes: C): Expand<MergeTypes<C>> {
  let obj: Partial<MergeTypes<C>> = {};
  for (const klass of classes) {
    obj = {
      ...obj,
      ...new klass()
    }
  }
  return obj as Expand<MergeTypes<C>>;
}

class A {
  public foo = 1
}

class B {
  public bar = "monkey"
}

class C {
  public yum = true;
}

const so = superObject(A, B, C)

Playground Link

  • Related