Recursive Partial<T> in TypeScript

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?


Solution 1:

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 ? RecursivePartial<T[P]> :
    T[P];
};

Reference:

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

Solution 2:

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.

Solution 3:

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}> = {};

Solution 4:

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