32

There are 2 types

type A = {
  x: number
  y: number
}

type B = {
  y: number
  z: number
}

How to get type with common properties of that types?

type C = Something<T1, T2> // { y: number }
kube
  • 13,176
  • 9
  • 34
  • 38
Kirill A
  • 441
  • 1
  • 5
  • 10

5 Answers5

44

Common Properties

Use static keyof operator:

type Ka = keyof A // 'x' | 'y'
type Kb = keyof B // 'y' | 'z'
type Kc = Ka & Kb // 'y'

And define a Mapped Type with properties in Kc:

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

This defines a new type where each key will be present both in A and B.

Each value associated to this key will have type A[K] | B[K], in case A[K] and B[K] are different.


Common Properties with Same Types only

Use Conditional Type to map key to value only if type same in A and B:

type MappedC = {
  [K in keyof A & keyof B]:
    A[K] extends B[K] // Basic check for simplicity here.
    ? K // Value becomes same as key
    : never // Or `never` if check did not pass
}

From this object, get union of all values, by accessing all keys:

// `never` will not appear in the union
type Kc = MappedC[keyof A & keyof B]

Finally:

type C = {
  [K in Kc]: A[K]
}
kube
  • 13,176
  • 9
  • 34
  • 38
  • Wow, it works! =) As I understood "in" operartor skips typings like 'x' & 'z'? Is there a way to force compiler to check equivalence of common property type? For example y should be a number – Kirill A Nov 19 '17 at 11:25
  • 1
    What skips `x` and `z` is the intersection of `keyof A` and `keyof B`. The *in* operator only tells that a key of `C` will be in this intersection. – kube Nov 19 '17 at 16:25
  • Could not find a way for intersecting only common types, but there's maybe a way. – kube Nov 19 '17 at 16:26
  • Unfortunately this seems to remove modifiers from the original types, e.g. if a property was optional, this will return a type with a required property. – Oliver Joseph Ash Dec 11 '18 at 13:12
  • @KirillA I added a solution to check that props types are same. – kube Dec 11 '18 at 22:08
  • Nice work! I was wondering, wouldn't the name "intersection type" be more accurate if it referred to this type instead of what the & type operator produces? After all in mathematics, an "intersection" excludes all the non-shared elements. – mmKALLL Sep 24 '20 at 11:36
14

Based on @kube's answer, you could use generics to create a reusable type:

type Common<A, B> = {
    [P in keyof A & keyof B]: A[P] | B[P];
}

This allows you to create intersections on the fly:

const c: Common<T1, T2> = { y: 123 };
Fabian Lauer
  • 8,891
  • 4
  • 26
  • 35
8

Shortcomings of a naive approach

While the below type is commonly suggested in the other answers:

type Common<A, B> = {
    [P in keyof A & keyof B]: A[P] | B[P];
}

It fails to check whether the property values are assignable to one another. This implies that it is possible for Common<A, B> to have properties that are not really shared by A and B.

type A = { a: number; x: string; z: number }
type B = { b: number; x: number; z: number}
Common<A, B> // => { x: string | number; z: number}
// above should just be { z: number }, since the type of property x is not
// assignable to the type of property x in both A and B.

The reason this sucks is because destructuring an object of type Common<A, B> into an object of either type A or type B can fail for either A or B. For example,

const sharedProps: Common<A, B> = { x: 'asdf', z: 9 }
const a: A = { ...sharedProps, a: 1 }

// below throws type error; string not assignable to number
const b: B = { ...sharedProps, b: 1 }

This is confusing, and puts an arbitrary limit on what we can do with the generic type.

In-depth approach that covers the above use-case

I suggest the below type (available for TS4.1+):

/**
 * Omits properties that have type `never`. Utilizes key-remapping introduced in
 * TS4.1.
 *
 * @example
 * ```ts
 * type A = { x: never; y: string; }
 * OmitNever<A> // => { y: string; }
 * ```
 */
type OmitNever<T extends Record<string, unknown>> = {
  [K in keyof T as T[K] extends never ? never : K]: T[K];
};

/**
 * Constructs a Record type that only includes shared properties between `A` and
 * `B`. If the value of a key is different in `A` and `B`, `SharedProperties<A,
 * B>` attempts to choose a type that is assignable to the types of both values.
 *
 * Note that this is NOT equivalent to `A & B`.
 *
 * @example
 * ```ts
 * type A = { x: string; y: string; }
 * type B = { y: string; z: string }
 * type C = { y: string | number; }
 *
 * A & B                  // => { x: string; y: string; z: string; }
 * SharedProperties<A, B> // => { y: string; }
 * SharedProperties<B, C> // => { y: string | number; }
 * ```
 */
type SharedProperties<A, B> = OmitNever<Pick<A & B, keyof A & keyof B>>;

This type correctly returns shared properties that are guaranteed to be subtypes of both A and B, since A & B is guaranteed to be a subtype of A and B so long as A & B is not never.

type A = { a: number; x: string; z: number }
type B = { b: number; x: number; z: number}
SharedProperties<A, B> // => { z: number }
dlq
  • 2,743
  • 1
  • 12
  • 12
4

Extract common props with same types only

Based on @kube's answer,

type Common<A, B> = Pick<
  A,
  {
    [K in keyof A & keyof B]: A[K] extends B[K]
      ? B[K] extends A[K]
        ? K
        : never
      : never;
  }[keyof A & keyof B]
>;
Equal
  • 354
  • 3
  • 9
0

This common type will be used to create a type that has all the keys of both types, but with the type of the value being the intersection of the two types.

type Common<A, B> = {
  [K in keyof A & keyof B]: A[K] extends B[K]
    ? B[K] extends A[K]
      ? A[K] // A[K] and B[K] are the same type
      : B[K] // A[K] is a subtype of B[K]
    : Common<A[K], B[K]> // A[K] and B[K] are not the same type, recurse
}

Here is an example of usage:

type A = {
  x: {
    i: string
    j: string
  }
  y: {
    l: string
    m: string
  }
  z: string
}

type B = {
  x: {
    j: string
    k: string
  }
  y: {
    m: string
    n: string
  }
}

type AB = Common<A, B> // { x: { j: string }, y: { m: string } }
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 24 '23 at 00:01