HaveSpacesuit
HaveSpacesuit

Reputation: 3994

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>?

Upvotes: 4

Views: 2404

Answers (2)

Mark Swardstrom
Mark Swardstrom

Reputation: 18070

You don't need to make it a generic

type MyObject = Record<string, string | string[] | number | boolean | IMyObject>
interface IMyObject extends MyObject {}

Upvotes: 1

jcalz
jcalz

Reputation: 327624

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

Upvotes: 9

Related Questions