typescriptgenericsrecursive-type

Recursive Generics in TypeScript


I want to declare a simple type to replace any in a piece of code. I know it will be some object-ish argument, so I could use Record<string, unknown>, but I'd rather be a little more specific.

This would be ideal:

type MyObject = Record<string, string | string[] | number | boolean | MyObject>

but it gives the error

Type alias 'MyObject' circularly references itself.ts(2456)

I can get around this by cheating with interfaces:

type MyObject<T> = Record<string, string | string[] | number | boolean | T>

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface IMyObject extends MyObject<IMyObject>{}

Is there a way to do what I want, or should I just stick to Record<string, unknown>?


Solution

  • The compiler gives up before it can realize that the type you are specifying is recursive in a supported way, because it does not probe the definition of the Record utility type before checking for circularity. This is a design limitation of TypeScript. See microsoft/TypeScript#41164 for an explanation.

    The fix here is to replace Record<string, XYZ> with what it eventually becomes, a type with a string index signature like { [k: string]: XYZ }:

    type MyObject = 
      { [k: string]: string | string[] | number | boolean | MyObject } // okay
    

    which works without error.

    Playground link to code