Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

how to implement `RecursiveReadonly <T>` with a "caveat" that if `T` is primitve return `T` instead of `RecursiveReadonly<T>`

just like in the question, is there a way to create a recursive Readonly <T> utility type which:

  • checks whether type T is primitive or extends {}
  • if type T extends {} recursively apply RecursiveReadonly <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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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

Playground link to code

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading