A type for getting properties common in all objects from a union

08 June 2022

Updated: 03 September 2023

Overview

Something that may come up in practice is a use for a type that allows us to enforce that an object has only the common properties for a given object

For example, given the two objects below:

1
type AThing = {
2
name: string
3
age: number
4
email: string
5
phone: number
6
}
7
8
type BThing = {
9
businessName: string[]
10
email: string
11
phone: string
12
}

I would like a type that contains only the things that these objects have in common, namely phone and email

A Type for Common Object Keys

This isn’t something that typescript has out-of-the-box, however it can be implemented by using some type juggling

First, we define a type called CommonKeys which gets us all the keys which are common in the two objects

1
type CommonKeys<T, R = {}> = R extends T
2
? keyof T & CommonKeys<Exclude<T, R>>
3
: keyof T

The CommonKeys type makes use of a condition to check if R which is the recursive type extends T which is the input type. Based on this, we cut down T one type at a time until there is no more object that can extend R, then for an input type T which is the same as R (an empty object) the result of CommonKeys<{}> will be never since {} has no keys, and will end the recursion

Applying this to the above types, we get:

1
type ABCommonKeys = CommonKeys<AThing | BThing>
2
// type ABCommonKeys = "email" | "phone"

And as a sanity check, we can also apply this to {}:

1
type Basic = CommonKeys<{}>
2
// type Basic = never

A Type for Common Object

Next, we can use the CommonKeys type defined above to create a Common type which wne used with the intersection will result in a type that has all the keys common in all types from the intersection

1
type Common<T> = Pick<T, CommonKeys<T>>

We can apply this now to a type of AThing | BThing like so:

1
type ABCommonObject = Common<AThing | BThing>
2
// type ABCommonObject = {
3
// email: string;
4
// phone: string | number;
5
// }

And we can see that we have the desired result which is an object with the properties that are common between both input object types

Final Solution

We can put the code from above together into the final solution which is just the two above types:

1
/** Gets the keys common to any type/union of `T` */
2
type CommonKeys<T, R = {}> = R extends T
3
? keyof T & CommonKeys<Exclude<T, R>>
4
: keyof T
5
6
/** Gets an object type containing all keys that exist within a type/union `T` */
7
type Common<T> = Pick<T, CommonKeys<T>>