Type-safe map of objects with corresponding names

Advertisements

Let’s say I have a generic type with the name property:

type Attribute<Name, Value> = {
    name : Name
    value : Value
}

The important parts here are that:

  1. The name parameter will be used as an exact string literal for the attribute’s name.
    For instance, Attribute<"width", number> instead of Attribute<string, number>.
  2. The real scenario the Attribute type will have much more properties, but for simplicity it has only value.

Now I want to have a map (object, dictionary, {}) which can map Attribute["name"] to the corresponding Attribute.
To put it simple, the map m of types Attribute<"width", number> and Attribute<"height", number> should return Attribute<"width", number> on accessing m.width and Attribute<"height", number> on m.height.

Like this:

function f(m : AttributesMap<[
    Attribute<"width", number>,
    Attribute<"height", number>,
]>) {
    m.width.name // "width" type
    m.height.name // "height" type
}

To achieve this I tried the following approach:

type AttributesMap<
    Attribute_ extends Attribute<string, any>[]
> = {
    [key in Attribute_[number][`name`]] : Attribute_[number]
}

But it returns Attribute<"width", number> | Attribute<"height", number> on accessing both m.width and m.height.

I understand that it does so because of the [number] property at the left is totally independent of the same [number] on the right.
But I cannot figure out how to make it work properly.

Any ideas?

>Solution :

You get a union because in the values you are accessing every value by using indexed access:

Attribute_[number]

Instead of this, let’s map through Attribute_[number]. Then we can map through the union using mapped type and change the key to name property by using the key remapping:

type AttributesMap<Attribute_ extends Attribute<string, any>[]> = {
  [K in Attribute_[number] as K['name']]: K;
};

Testing:

function f(
  m: AttributesMap<[Attribute<'width', number>, Attribute<'height', number>]>,
) {
  m.width.name; // "width" type
  m.height.name; // "height" type
}

playground

Leave a ReplyCancel reply