TypeScript Version: 3.3.3333
Search Terms: deep partial any DeepPartial
Code
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };
type User = { name: string; age: number; }
type Test = { user: User; query: any };
const test: DeepPartial<Test> = {
user: {
name: 'bob',
},
// Unexpected error: Type 'string' is not assignable to type 'DeepPartial<any> | undefined'.
query: { foo: 'string' },
};
type Test2 = any;
const test2: DeepPartial<Test2> = {
// Unexpected error: Type 'string' is not assignable to type 'DeepPartial<any> | undefined'
foo: 'bar'
};
You always need an exit condition for recursive definitions:
type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
First, as @dawidgarus says above, it is definitely a good idea to have an explicit exit condition for recursive types.
Your example is working as intended. keyof any is string | number | symbol, so a mapped type applied to any yields the equivalent of { [x: string]: XXX }. In your case you therefore end up with an infinite expansion { [x: string]: DeepPartial<any> }.
You could argue that we should just yield any when a homomorphic mapped type is applied to any, but that would be less precise. For example { [K in keyof T]: number } instantiated with T as any really ought to yield { [x: string]: number } and not just any.
Thanks
Most helpful comment
You always need an exit condition for recursive definitions: