ecstrema
ecstrema

Reputation: 691

Typescript generics: readonly values from interface children

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.

Upvotes: 0

Views: 444

Answers (2)

ZZB
ZZB

Reputation: 810

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

Upvotes: 1

Mack
Mack

Reputation: 771

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

Upvotes: 3

Related Questions