In this question on implementing a recursive partial in typescript, we get some answers that look good... except the latest answer points out they are all incomplete.
Let's take a closer look with examples:
These are three of the proposed solutions:
//The simple one
type SolutionA<T> = {
[P in keyof T]?: SolutionA<T[P]>;
};
//The 'complete' one
type SolutionB<T> = {
[P in keyof T]?:
T[P] extends (infer U)[] ? SolutionB<U>[] :
T[P] extends object ? SolutionB<T[P]> :
T[P];
};
//The one that accounts for Date
type SolutionC<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 : SolutionC<T>;
And here is the example that shows A and B are incomplete:
type TT = { dateValue: Date }
const x1: SolutionA<TT> = { dateValue: "0" } // counterintuitively allowed
const x2: SolutionB<TT> = { dateValue: "0" } // counterintuitively allowed
const x3: SolutionC<TT> = { dateValue: "0" } // correctly disallowed by ts
Why does this happen? Is there a way to make a recursive partial without having to manually include every single exception like Maps, Sets, etc? What's the common thread between these 'exceptional' types? Should I worry about someone creating their own exception that has to be added to the list?