Calvein
Calvein

Reputation: 2121

Map type based on itself

I'm really sorry for the title but I actually don't know what I'm looking for. I have a type with 2 properties and I want one of those to be related to the other one.

Here's an example:

My first type which is a map of types:

type TextApiFormat = {
  title: string
}

type ImageApiFormat = {
  src: string
}

export type ApiFormats = {
  text: TextApiFormat
  image: ImageApiFormat
}

My second type:

export type Block = {
  id: string
  type: keyof ApiFormats
}

And the result I would like to have:

export type EditBlock = {
  block: Block
  apiFormat: ApiFormats[Block.type]
}

Basically, I want my the value of apiFormat to be change based on the value of block.type, I doubt it's unrealisable but I'm stuck on it.

Upvotes: 1

Views: 116

Answers (1)

jcalz
jcalz

Reputation: 327634

In order for this to work, you need EditBlock to be a union of block/apiFormat pairs for each key of ApiFormats. First, let's allow Block to have a specific type property corresponding to a particular key from ApiFormats, by making it generic:

export type Block<K extends keyof ApiFormats> = {
  id: string
  type: K
}

Then you want EditBlock to be the following type:

type EditBlock = {
    block: Block<"text">;
    apiFormat: TextApiFormat;
} | {
    block: Block<"image">;
    apiFormat: ImageApiFormat;
}

This meets your criterion; if you have an EditBlock and the block property is Block<"text">, then the apiFormat property must be TextApiFormat. And similarly for Block<"image">.

Note that this type is unfortunately not considered a discriminated union by the TypeScript compiler, so you might have a hard time narrowing by checking editBlock.block.type; there is currently no support for "nested" discriminated unions as requested by microsoft/TypeScript#18758. But this is the type you asked for.


It is possible to make the compiler compute EditBlock as a function of ApiFormats so that if you modify ApiFormats then EditBlock will automatically update itself. One way to do it is to write EditBlock as a distributive object type as coined in microsoft/TypeScript#47109:

type EditBlock = { [K in keyof ApiFormats]:
  { block: Block<K>, apiFormat: ApiFormats[K] }
}[keyof ApiFormats]

Here we are first computing a mapped type with keys text and image (from keyof ApiFormats) whose property value types are the relevant block/apiFormat object types. And then we immediately index into that mapped type with keyof ApiFormats to get a union of the property types. You can verify that this is the same type as written out manually above.

Playground link to code

Upvotes: 1

Related Questions