I have this interface, that just stores a key of another interface (modelKey
) and the value of that key (value
):
interface ValueHolder<T, H extends keyof T> {
modelKey: H;
value: T[H];
}
Now I want to store the horsePower
from the following model, with the matching type in a ValueHolder
:
interface Car {
id: number;
horsePower?: number;
date: Date;
};
This looks like this:
const test: ValueHolder<Car, keyof Car> = {
modelKey: 'horsePower',
value: 1000,
};
At this point no error happens and it would store the value fine. But you could also pass a value of type Date
:
const test: ValueHolder<Car, keyof Car> = {
modelKey: 'horsePower',
value: new Date(),
};
Because for whatever reason the value can accept all types of any key in the model provided:
(property) ValueHolder<Car, keyof Car>.value: string | number | Date | undefined
How can I make the value
key of the interface ValueHolder
only accepts values of type undefined | number
, if you provide the modelKey
horsePower
?
CodePudding user response:
ValueHolder
second generic argument H
allows all keys which in turn leads to allowing all values.
You need slightly modify your main utility type to make illegal state unrepresentable.
Consider this example:
type Values<T> = T[keyof T]
type ValueHolder<T> = Values<{
[Prop in keyof T]: {
modelKey: Prop;
value: T[Prop]
}
}>
// type Test = {
// modelKey: "id";
// value: number;
// } | {
// modelKey: "horsePower";
// value: number | undefined;
// } | {
// modelKey: "date";
// value: Date;
// } | undefined
type Test = ValueHolder<Car>
interface Car {
id: number;
horsePower?: number;
date: Date;
};
// ok
const test: ValueHolder<Car> = {
modelKey: 'horsePower',
value: 1000,
};
// error
const test2: ValueHolder<Car> = {
modelKey: 'horsePower',
value: new Date(),
};
See type Test
. ValueHolder
creates a union of all allowed values. It iterates through each key and creates this interface {Prop:{modelKey:P, value:T[P]}}. Then Values
obtains each object value {modelKey:P, value:T[P]}
and makes a union of them.
UPDATE
Thanks, works fine! What if I want that
T[Prop]
can only be of the type string? Is that also possible?
Yes it is. There is two ways to achieve it. You can allow ValueHolder
only to receive objects where values are strings.
type ValueHolder<T extends Record<string, string>> = Values<{
[Prop in keyof T]: {
modelKey: Prop;
value: T[Prop]
}
}>
Or, you can check inside iteration whether T[Prop]
is string or not.
type ValueHolder<T> = Values<{
[Prop in keyof T]: T[Prop] extends string ? {
modelKey: Prop;
value: T[Prop]
} : never
}>