0

Given this input:

export type Test = {
    one: {
        a: string;
        b: string;
        c: string;
    };
    two: {
        a: string;
        b: string;
        d: string;
    };
}

I need a generics like CombinedChildren<T> which outputs the following type:

export type Combined = {
    a?: string;
    b?: string;
    c?: string;
    d?: string;
}

Basically, it takes the children properties and combines them, including them even if they're not present in all children.

Tried

export type KeyOfTest = Partial<Test[keyof Test]>
export type MappedKeyOfTest = Partial<{
    [key in keyof Test[keyof Test]]: Test[keyof Test][key]
}>

But none output exactly what I want.

ecstrema
  • 543
  • 1
  • 5
  • 20

2 Answers2

3

We can break this down into a few steps. First, we get a union of all the child types by Test[keyof Test]. Then we want a union of all this type, so we use a utility type that converts union types to intersection types (taken from this answer by @jcalz). Finally, we apply Partial to the resulting type.

export type Test = {
    one: {
        a: string;
        b: string;
        c: string;
    };
    two: {
        a: string;
        b: string;
        d: string;
    };
    three: {
      a: string;
      e: number;
    }
}

// union to intersection converter by @jcalz: https://stackoverflow.com/a/50375286/8580499
// Intersect<{ a: 1 } | { b: 2 }> = { a: 1 } & { b: 2 }
type Intersect<T> = (T extends any ? ((x: T) => 0) : never) extends ((x: infer R) => 0) ? R : never;

// Combined = { a?: string; b?: string; c?: string; d?: string; e?: number }
type Combined = Partial<Intersect<Test[keyof Test]>>;

Playground link

Mack
  • 691
  • 3
  • 7
  • 1
    This might not be working as expected due to conflicts between types. Try changing in second object type of a to number for example. The result would be a?: undefined – ZZB Sep 27 '21 at 16:15
  • @ZZB I don't think there's any reasonable output in that case without a more detailed specification. Do we want `a?: string | number` or do we omit `a`, or something else? – Mack Sep 28 '21 at 03:27
  • It would be interesting to see an answer with merged types like "string" | "number". I have tried to do it however it turned out to be more tricky than I thought – ZZB Sep 28 '21 at 07:55
  • 1
    Here is an answer to this problem https://stackoverflow.com/questions/56296506/typescript-generic-interface-as-union-of-other-interfaces – ZZB Sep 28 '21 at 08:59
1

In case you want to also merge all types for example string | number then here is a solution

export type Test = {
    one: {
        a: string;
        b: string;
        c: string;
    };
    two: {
        a: number;
        b: string;
        d: string;
    };
    three: {
      a: string;
      e: number;
    }
}

type AllNested = Test[keyof Test]

type KeyOf<T> = T extends any ? keyof T : never

type PropValue<T, K extends PropertyKey> = T extends Record<K, infer V> ? V : never

type Merged<T> = {
    [P in KeyOf<T>] : PropValue<T, P>
}

type MergedType = Merged<AllNested>

Link to the playground

Solution is based on this brilliant answer

ZZB
  • 790
  • 6
  • 11