just like in the question, is there a way to create a recursive Readonly <T> utility type which:
- checks whether type
Tis primitive or extends{} - if type
Textends{}recursively applyRecursiveReadonly <T>to its properties
pseudo code:
type RecursiveReadonly <T> = {
readonly [P in keyof T]:
T === object ?
RecursiveReadonly <T[P]> :
T[P]
};
PS. normally this should work
type RecursiveReadonly <T> = {
readonly [P in keyof T]: RecursiveReadonly <T[P]>
};
however… type FOO = RecursiveReadonly <{ foo: boolean }>; creates type: type FOO = { readonly foo: RecursiveReadonly <boolean>, and in some obscure cases (which I am unable to provide right now) tsc throws error RecursiveReadonly <boolean> is not assignable to boolean. I think this applies only to primitive types, hence boolean type used in this example is a placeholder for all primitive types.
>Solution :
The syntax you’re looking for is a conditional type of the form T extends U ? X : Y (so extends and not ===). You can make that change and your code will work, but when you inspect RecursiveReadonly<{ foo: boolean }> it will still display { readonly foo: RecursiveReadonly<boolean> }. This really is equivalent to { readonly foo: boolean }, even in your original unconditional version, but I’ll take your word that you’ve run into some situation where the compiler balks at it.
To get the type you want, I’d move the conditional type to the top level of your utility type, like this:
type RecursiveReadonly<T> = T extends object ? {
readonly [P in keyof T]: RecursiveReadonly<T[P]>
} : T
That gives the type checker a hint that you want to see RecursiveReadonly<T> displayed as an "expanded" type (see How can I see the full expanded contract of a Typescript type? ). Now you’ll see the type you expect:
type FOO = RecursiveReadonly<{ foo: boolean }>;
// type FOO = { readonly foo: boolean; }