Reputation: 23
I'm attempting to use a mapped type to provide more type safety when using union types in a map. There does not seem to be a way to provide type safety between the key/value when using a property type (e.g. ['value']
) as the type for the key (K
).
I would like to avoid creating a unique model by hand to allow for this.
Code:
interface IAction { value: string; }
type ActionMapper<A extends IAction> = {
[K in A['value']]: A;
}
interface IActionOne { value: 'action_one' }
interface IActionTwo { value: 'action_two' }
type Actions = IActionOne | IActionTwo;
const reducerMap: ActionMapper<Actions> = {
action_one: { value: 'action_one' },
action_two: { value: 'action_one' }, // expecting this line to fail
}
I have commented the line I expected to fail.
I feel I should be able to leverage the key (K in
) to provide the correct type as the value. However, I currently use A
, which provides the IAction
implementation where the value
is of type string
- I want to avoid this.
Is this possible in the current version of TypeScript?
Upvotes: 1
Views: 588
Reputation: 329418
Yes, it is possible to do what you want.
As you note, your problem is that the values of properties of ActionMapper<A>
is always A
. For Actions
, that's a union type. What you really want to do is extract from A
the constituent that matches {value: K}
for each key K
. Luckily, there is a pre-defined conditional type named Extract
that does this for you. Let's redefine ActionMapper<A>
:
type ActionMapper<A extends IAction> = {
[K in A['value']]: Extract<A, {value: K}>;
}
Now we will try again:
const reducerMap: ActionMapper<Actions> = {
action_one: { value: 'action_one' },
action_two: { value: 'action_one' }, // error!
// Type '"action_one"' is not assignable to type '"action_two"'.
}
And you get the error you expected. Hope that helps. Good luck!
Upvotes: 1