Home > Mobile >  Create type having common props from other types and other optional
Create type having common props from other types and other optional

Time:03-17

I have following three interfaces:

type A = {
    x: string
    y: string
}

type B = {
    y: string
}

type C = {
    z: string;
    y: string;
}

There are common required properties here. And there're required properties which are not part of the rest of 2 types. So, my question is:

Is there way to create type that unites common props and makes other properties optional? E.g.:

type D = {
    y: string
    x?: string
    z?: string
}

Any ideas?

CodePudding user response:

There's quite a few ways to do this, but one solution would be to create a type Pick<T1, keyof T1 & keyof T2 & keyof T3> that has the properties shared by three types T1 to T3, and intersect it with an object Partial<T1 & T2 & T3> that has optional properties for any properties in T1 to T3.

type CommonRequiredRestOptional<T1, T2, T3> =
  Pick<T1, keyof T1 & keyof T2 & keyof T3> & Partial<T1 & T2 & T3>

The partial object will also contain the required properties, but because of the intersection these stay required (T & (T | undefined) is T).

A helper TopLevelNormalize can be used to show the actual object type rather than Pick<A, "y"> & Partial<A & B & C>:

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

type Test = TopLevelNormalize<CommonRequiredRestOptional<A, B, C>>
// type Test = { y: string; x?: string | undefined; z?: string | undefined;}

TypeScript playground

CodePudding user response:

Whilst working on this solution, I tried to create an implementation that used generics but didn't get anywhere. I'm sure that a TypeScript wizard could derive a generic solution from mine!

First, let's create a utility type for finding properties that are "common":

type Common = {
  [key in keyof A & keyof B & keyof C]: A[key] | B[key] | C[key]
}

Now we can find properties from one of your interfaces that are "uncommon":

{
  // Properties only in A
  [key in keyof Exclude<A, keyof Common>]?: A[key]
}

Putting this altogether, we get this:

export interface A {
  x: string
  y: string
}

export interface B {
  y: string
}

export interface C {
  z: string
  y: string
}

/**
 * Common properties in A, B, and C.
 */
type Common = {
  [key in keyof A & keyof B & keyof C]: A[key] | B[key] | C[key]
}

/**
 * All properties from A, B, and C.
 * 
 * Properties that are "common" (in all interfaces) are required,
 * others (not present in all interfaces) are optional.
 */
type D = Common & {
  // Properties only in A
  [key in keyof Exclude<A, keyof Common>]?: A[key]
} & {
  // Properties only in B
  [key in keyof Exclude<B, keyof Common>]?: B[key]
} & {
  // Properties only in C
  [key in keyof Exclude<C, keyof Common>]?: C[key]
}

TS Playground

CodePudding user response:

I wanted to share a more general answer just in case someone finds it useful.

Quite extensive, but in the end you will only need to use the CommonUncommon type (for lack of a better name)

export interface A {
  x: string;
  y: string;
}

export interface B {
  y: string;
}

export interface C {
  z: string;
  y: string;
}

export interface D {
  y: string;
  justAnotherProp: unknown[];
}

type IsInAll<T extends unknown[], Prop extends number | string | Symbol> = T extends [infer Head, ...infer Tail]
  ? Prop extends keyof Head
    ? IsInAll<Tail, Prop>
    : false
  : true;
type ExcludeArr<T extends unknown[], X, Res extends unknown[] = []> = T extends [infer Head, ...infer Tail]
  ? Head extends X
    ? X extends Head
      ? ExcludeArr<Tail, X, Res>
      : ExcludeArr<Tail, X, [...Res, Head]>
    : ExcludeArr<Tail, X, [...Res, Head]>
  : Res;
type Normalize<T> = {[K in keyof T]: T[K]};
type Unite<T extends unknown[], K extends unknown[] = T, Res = unknown> = T extends [infer Head, ...infer Tail] 
  ? Unite<Tail, K, {
    [Prop in keyof Head as true extends IsInAll<ExcludeArr<K, Head>, Prop> ? Prop : never]: Head[Prop]; 
  } 
  & 
  {
    [Prop in keyof Head as false extends IsInAll<ExcludeArr<K, Head>, Prop> ? Prop : never]?: Head[Prop]; 
  }
  &
  Res>
  : Res;

type CommonUncommon<T extends unknown[]> = Normalize<Unite<T>>;

type res1 = CommonUncommon<[A, B]>;
type res2 = CommonUncommon<[A, B, C]>;
type res3 = CommonUncommon<[A, B, C, D]>;

Playground

  • Related