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