DeepPartial in Typescript

Jul 10, 2023

typescript
javascript

Partial is a very useful utility type in Typescript. It allows you to make all properties of an existing type optional. However, by default, TypeScript’s Partial<T> only performs a shallow partial type, meaning it makes the top-level properties optional, but it does not recurse into nested properties.

First, here’s the working example:

interface User {
	name: string;
	age: number;
	role: string;
}

const newUser: Partial<User> = {
	name: 'Josh'
};
// No TS error because all properties from `User` are optional

The problem arises when we declare a more complex type that has nested objects.

interface User {
	name: string;
	age: number;
	role: string;
	location: {
		country: string;
		countryCode: string;
		street: string;
	};
}

const newUser: Partial<User> = {
	name: 'Josh',
	location: {
		countryCode: 'GB'
	}
};
// Type '{ countryCode: string; }' is missing the following properties from type '{ country: string; countryCode: string; // street: string; }': country, street; ts(2739)

Having an object with nested properties, we should declare all properties of location to satisfy the compiler. Partial does not work here anymore. To create a deep partial type, which makes all properties throughout the object hierarchy optional, we can define our own DeepPartial utility type using recursive conditional types. Here’s an example implementation:

type DeepPartial<T> = {
	[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

Breaking down the parts of this:

  • keyof T obtains all the keys (properties) of type T.
  • T[P] accesses the type of the property P in T.
  • T[P] extends object ? DeepPartial<T[P]> : T[P] checks if the property is an object type. If it is, then we recursively apply DeepPartial to that property’s type. Otherwise, we use the original type of the property.
  • [P in keyof T]? makes each property optional by appending a question mark.

Now we can use it receive expected result

const deepPartialUser: DeepPartial<User> = {
	name: 'Josh',
	role: 'Admin',
	location: {
		street: 'Elm Street'
	}
};

In the above example, deepPartialUser has a deep partial type of the User interface. It allows you to omit any property at any level of nesting, making them all optional.