user4301448
user4301448

Reputation: 320

How do I make a type-level list of varying Generics in TypeScript?

Given this type:

type AThingWithGenerics<A, B, C> = {
  name: string;
  someFunction: (id: number) => A;
  anotherFunction: () => B;
  loveSomeGenerics: (s: string) => { foo: C };
};

Where A, B, and C are objects that vary across values. For example:

const aThing: AThingWithGenerics<{ foo: string }, { foo: number, bar: string }, { bar: string }> = {
  name: "a_thing",
  someFunction: (id: number) => { return { foo: "bar" }; },
  anotherFunction: () => { return { foo: 3, bar: "foo" } },
  loveSomeGenerics: (s: string) => { return { foo: { bar: "In this case C is an object containing a string" + s } }; },
}

const anotherThing: AThingWithGenerics<{ bar: number }, { aString: string }, { bar: "test" }> = {
  name: "another_thing",
  someFunction: (id: number) => { return { bar: id + 1 }; },
  anotherFunction: () => { return { aString: "test" } },
  loveSomeGenerics: (s: string) => { return { foo: { bar: "test" } } },
}

It's possible to create a tuple of specific AThingWithGenerics:

type ASpecificPileOfThings = [typeof aThing, typeof anotherThing];

It's also possible to make a list of AThingWithGenerics as long as A, B, and C do not vary:

type APileOfTheSameThing<A, B, C> = [AThingWithGenerics<A, B, C>];

But I am trying to add a list of related AThingWithGenerics to AThingWithGenerics, so aThing might include [anotherThing] as its related. The problem is A, B, and C in related will vary.

So how do I create a type that contains varying generics? This is what I have so far:

type AThingWithGenericsII<A, B, C> = {
  name: string;
  someFunction: (id: number) => A;
  anotherFunction: () => B;
  loveSomeGenerics: (s: string) => { foo: C };
  related: Array<AThingWithGenericsII<A,B,C>>
}

const anotherThingWithRelated: AThingWithGenericsII<{ bar: number }, { aString: string }, { bar: "test" }> = {
  name: "another_thing",
  someFunction: (id: number) => { return { bar: id + 1 } },
  anotherFunction: () => { return { aString: "test" } },
  loveSomeGenerics: (s: string) => { return { foo: { bar: "test" } } },
  related: []
}

const aThingWithRelated: AThingWithGenericsII<{foo: string, bar: string }, { property: string }, { nestedFoo: number }> = {
  name: "a_thing_with_related",
  someFunction: (id: number) => { return { foo: "This is a string", bar: "" } },
  anotherFunction: () => { return { property: "bar" } },
  loveSomeGenerics: (s: string) => { return { foo: { nestedFoo: 3.1 } } },
  related: [anotherThingWithRelated]
}

This does not typecheck, since A, B, and Cs types do not unify. How can AThingWithGenericsII be changed so aThingWithRelated type checks?

TypeScript playground link. I'm sure this question about recursive Generics is related, but I couldn't get it to work for my case.

Upvotes: 1

Views: 100

Answers (1)

bloo
bloo

Reputation: 1560

Try using the first interface AThingWithGenerics you have defined but with this added in, you do not need the others :

type AThingWithGenerics<A = any, B = any, C = any> = {
  name: string;
  someFunction: (id: number) => A;
  anotherFunction: () => B;
  loveSomeGenerics: (s: string) => { foo: C };

  // some things might not have relatives
  related?: Array<AThingWithGenerics>
};

Using type any with the generic definitions will make typescript infer the types of generics used in a specific instance. We do this to allow the related property to use any generics that will ultimately be inferred by typescript.

Just to add the sentiment behind the interface, I figured not all "things" will be related so that is why it is possibly undefined, which was the initial ts error found on the link you provided.

Playgound

Upvotes: 1

Related Questions