102

I have this interface:

export interface UserSettings
{
    one: {
        three: number;
        four: number;
    };
    two: {
        five: number;
        six: number;
    };
}

...and want to turn it into this:

export interface UserSettingsForUpdate
{
    one?: {
        three?: number;
        four?: number;
    };
    two?: {
        five?: number;
        six?: number;
    };
}

...but Partial<UserSettings> produces this:

{
    one?: {
        three: number;
        four: number;
    };
    two?: {
        five: number;
        six: number;
    };
}

Is it possible to use mapped types to make all the properties on all depths optional, or do I have to create an interface manually for that?

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
Christer Carlsund
  • 1,131
  • 2
  • 7
  • 9

5 Answers5

161

With the landing of Conditional Types in 2.8, we can now declare a recursive partial type as follows.

type RecursivePartial<T> = {
  [P in keyof T]?:
    T[P] extends (infer U)[] ? RecursivePartial<U>[] :
    T[P] extends object | undefined ? RecursivePartial<T[P]> :
    T[P];
};

Reference:

http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html

Mehmet Ataş
  • 11,081
  • 6
  • 51
  • 78
Jeffrey Patterson
  • 2,342
  • 1
  • 13
  • 9
  • 19
    This is great. It would be very nice if this declaration shipped with typescript in lib.d.ts. – Jason Hoetger Sep 17 '18 at 17:18
  • 1
    Just a note, the second condition was too restrictive for me, where some types that must not match the `object` type where requiring all properties to be filled out instead of partially. I simplified it to `T[P] extends (infer U)[] ? RecursivePartial[] : RecursivePartial` – kamranicus Apr 24 '19 at 19:57
  • 14
    This won’t work if any of the properties are already optional. Replace `T[P] extends object` with `T[P] extends (object | undefined)` to fix. – Jackson Jul 02 '19 at 00:38
  • 1
    This broke for me after upgrading to typescript 3.8.2 for sub objects with the type `interface Foo { readonly [id: string]: ReadonlyArray; }` with the error `Type 'undefined' is not assignable to type 'readonly Bar[]'.` – GentlemanHal Feb 22 '20 at 22:54
  • This type lead to a difficult-to-trace situation where `tsc` would run for 30 minutes and eventually gave an out of memory error. The answer with `tsdef` below did work however. – Bart Mar 25 '20 at 11:32
  • This works fine until you have a type like `NodeJS.ReadStream` (or `WriteStream`), which gives the error `Property '[Symbol.asyncIterator]' is missing in type 'RecursivePartial' but required in type 'ReadStream'.` - I ended up using the [tsdef](https://stackoverflow.com/a/56403542/8602926) package, which doesn't have this issue. – Sv443 Mar 31 '21 at 13:26
  • 1
    The `object` type is flagged by eslint, so I used this instead: `type RecursivePartial = { [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial[] : T[P] extends (number | string | symbol | undefined) ? T[P] : RecursivePartial; };` – Hayden Linder Mar 01 '22 at 22:54
  • Why is it necessary to add the `T[P] extends object ? RecursivePartial : T[P];` ? Isn't it enough to just use `RecursivePartial` directly? – leinaD_natipaC Aug 22 '22 at 18:31
  • To fix the issue with types that are already optional it's interesting to use the TypeScript's native NonNullable type. `[P in keyof NonNullable]`, `NonNullable extends (infer U)[]`, `NonNullable extends object`. – axell-brendow Nov 19 '22 at 00:43
44

you could make your own mapping type, like this:

type RecursivePartial<T> = {
    [P in keyof T]?: RecursivePartial<T[P]>;
};

enter image description here

Unfortunately, this does not work for array-typed fields. There does not seem to be a way to do conditional type mapping yet either; i.e. limit to primitives. See https://github.com/Microsoft/TypeScript/pull/12114#issuecomment-259776847 Its possible now, see other answer.

Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122
  • Nice, thanks! However, if I change the `Bar` interface to `interface Bar { names: string[]; }` then VS Code tells me that `names` has the type `RecursivePartial` instead of `string[]`. Any thoughts? – Christer Carlsund Feb 01 '17 at 15:03
  • I suspect this is a current limitation: see https://github.com/Microsoft/TypeScript/pull/12114#issuecomment-259776847, https://github.com/Microsoft/TypeScript/issues/12424 and https://github.com/Microsoft/TypeScript/issues/13257 – Meirion Hughes Feb 01 '17 at 15:43
  • I'll watch those issues and update this answer when they are resolved. – Meirion Hughes Feb 02 '17 at 09:51
18

I've made a library, tsdef that has many common patterns / snippets like this.

For this case, you can use it like the following:

import { DeepPartial } from 'tsdef';

let o: DeepPartial<{a: number}> = {};
Joon
  • 9,346
  • 8
  • 48
  • 75
  • 1
    Very nice library, with a lot of examples to learn from. Source is here https://github.com/joonhocho/tsdef/blob/master/src/index.ts – John Leidegren Jan 21 '22 at 11:02
9

Neither of the provided solutions is good enough. Here is an explanation:

const x: RecursivePartial<{dateValue: Date}> = {dateValue: 0}; // ja-ja-ja

In the code above the actual type of the dateValue is RecursivePartial<Date> | undefined which allows to assign any value!!! Expected type of the dateValue is just Date, however the rule T[P] extends object ? RecursivePartial<T[P]> is too broad.

The solution is to separate primitives and eliminate extends object:

export type RecursivePartial<T> = {
    [P in keyof T]?:
    T[P] extends Array<infer U> ? Array<Value<U>> : Value<T[P]>;
};
type AllowedPrimitives = boolean | string | number | Date /* add any types than should be considered as a value, say, DateTimeOffset */;
type Value<T> = T extends AllowedPrimitives ? T : RecursivePartial<T>;
1

thanks to @jefferey-peterson and @pavel-husakouski, my implementation is similar by ignoring primitive types and it's array as is with fallback to recursive partial.

/** ADD YOUR OWN SELECTION OF PRIMITIVES **/
type Primitives =
  | boolean
  | number
  | bigint
  | string
  | symbol
  | void
  | null
  | undefined
  | Date
  | Buffer
  | Function
  | RegExp;
 
type RecursivePartial<T> = T extends Primitives
  ? T /** RESOLVE PRIMITIVE TO ITSELF */
  : T extends Array<infer U>
  ? Array<RecursivePartial<U>> /** RESOLVE ARRAY */
  : T extends Map<infer K, infer V>
  ? Map<RecursivePartial<K>, RecursivePartial<V>> /** RESOLVE MAP */
  : T extends WeakMap<infer K, infer V>
  ? WeakMap<RecursivePartial<K>, RecursivePartial<V>> /** RESOLVE WEAK-MAP */
  : T extends Set<infer V>
  ? Set<RecursivePartial<V>> /** RESOLVE SET */
  : T extends WeakSet<infer V>
  ? WeakSet<RecursivePartial<V>> /** RESOLVE WEAK-SET */
  : T extends object
  ? {
      [K in keyof T]?: RecursivePartial<T[K]> /** RESOLVES OBJECT */;
    }
  : T; /** FALLBACK TO ITSELF IF NOT HANDLED */
Ghani
  • 81
  • 7