Ko-Fi?

DeepPartial in Typescript

map screenshot

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 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.