Home > Mobile >  How can I make a generic type in which the intersection is required and the diff is optional
How can I make a generic type in which the intersection is required and the diff is optional

Time:01-26

In typescript, given 2 object types that has common fields yet are not related, I would like to create a new type where the joined fields are mandatory and the fields that do not exist in both are optional.

How can this be done?

CodePudding user response:

Use DiffAsPartial described below:

export type Union<T1, T2> = T1 & T2;
type KeysOfType<T, SelectedType> = {
    [key in keyof T]: SelectedType extends T[key] ? key : never;
}[keyof T];

type PickOptionals<T> = Partial<Pick<T, KeysOfType<T, undefined>>>;
type PickRequired<T> = Omit<T, KeysOfType<T, undefined>>;

export type Intersection<T1, T2> = {
    [K in Union<keyof PickOptionals<T1> & string, keyof PickOptionals<T2> & string>]?: PickOptionals<T1>[K] | PickOptionals<T2>[K];
} & {
    [K in keyof PickRequired<T1> & keyof PickRequired<T2>]: PickRequired<T1>[K] | PickRequired<T2>[K];
};
export type Diff<T1, T2> = Omit<Union<T1, T2>, keyof Intersection<T1, T2>>;

export type DiffAsPartial<T, S> = Intersection<T, S> & Partial<Diff<T, S>>;

Usage example:

type Type1 = {
    a: string,
    b?string,
    c:number,
}
type Type2 = {
    b?string,
    c:string,
    d: string,
}

type RequiredType = DiffAsPartial<Type1, Type2>;

// == {a?:string, b?:string, c: number | string, d?:string}

CodePudding user response:

Alternative implementation of DiffAsPartial that may be more readable to some:

// grabs all keys of T that can be undefined
type KeysWithUndefined<T> = { [K in keyof T]: undefined extends T[K] ? K : never }[keyof T];
// excluding keys with undefined from keys of T gives us the keys without undefined
type KeysWithoutUndefined<T> = Exclude<keyof T, KeysWithUndefined<T>>;

// converts keys with undefined to optional keys
type UndefinedToOptional<T> = ({
    [K in KeysWithUndefined<T>]?: Exclude<T[K], undefined>;
} & {
    [K in KeysWithoutUndefined<T>]: T[K]; 
}) extends infer O ? { [K in keyof O]: O[K] } : never; // simplify intersection

type DiffAsPartial<A, B> = UndefinedToOptional<{
    [K in keyof A | keyof B]: // for all the keys in A and B
        // if the key exists in both A and B, then the type is A[K] | B[K]
        K extends keyof A & keyof B ? (A | B)[K] :
        // if the key exists only in A, then it should be A[K] or undefined
        K extends keyof A ? A[K] | undefined :
        // if the key exists only in B, then it should be B[K] or undefined
        K extends keyof B ? B[K] | undefined :
        // this case should never happen
        never;
}>;

This is probably slower than @NoyOliel's answer because of the extensive use of conditional types, but for reasonably sized inputs this should not be noticeable.

Playground

  • Related