Reputation: 11
I'm currently writing some typedefs and classes for a validation component, which is being constructed to validate fields on a specific class ('Main' below). The objective:
MainValidationRule
) should contain the key (T extends keyof Main
) that it's validating, as well as a validation function that validates the corresponding value and returns a flag indicating validity ((value: Main[T]) => boolean
).T in keyof Main
) to an array of all the validation rules for that field (Array<MainValidationRule<T>>
).However, when I try to map the rules into the desired structured (code below), TypeScript gives a type error:
I would have assumed that, because any given value of rule.key
guarantees that rule
will satisfy the constraints (ie. we know that if rule.key === 'foo'
, then rule
will satisfy the type MainValidationRule<'foo'>
, and the same is true of 'bar' or any other key that might be added to Main
), this would compile fine. But instead TypeScript seems to be checking whether all possible values for MainValidationRule<keyof Main>
are valid rules for a specific key, which fails as (for example) MainValidationRule<'bar'>
isn't a valid rule if the key is 'foo'
- despite the fact our constraints mean that will never be possible.
Am I doing something wrong? Or is there another way I can write this that would lead TypeScript to correctly infer that the constraint is satisfied? The Main
class is updated frequently with new properties, so manually typing out and checking every possible variation is impractical. Code below. Thanks in advance!
type Main = {
foo: string;
bar: number;
};
type MainValidationRule<T extends keyof Main> = {
key: T;
isValid: (value: Main[T]) => boolean;
};
type MainValidationRulesMap = { [ T in keyof Main ]?: Array<MainValidationRule<T>> };
const mainValidationRulesMap: MainValidationRulesMap = {};
const mainValidationRules: Array<MainValidationRule<keyof Main>> = [];
mainValidationRules.forEach(rule => {
mainValidationRulesMap[rule.key] = [ rule ]; // type error
});
Upvotes: 1
Views: 662
Reputation: 33041
Some time it is bette to use union type instead of Foo<keyof Bar>
To make it clear, just compare my MainValidationRule
and MainValidationRule<keyof Main>
.
It seams that they are equal, but it is not true.
For TS it is much easier to infer simple union type.
Here is the code:
type Main = {
foo: string;
bar: number;
};
type Values<T> = T[keyof T]
type MainValidationRule = Values<{
[P in keyof Main]: {
key: P;
isValid: (value: Main[P]) => boolean;
}
}>
type Rules = Array<MainValidationRule>
type MainValidationRulesMap = Partial<{ [T in keyof Main]: Rules }>;
const mainValidationRulesMap: MainValidationRulesMap = {};
const mainValidationRules: Rules = [];
mainValidationRules.forEach(rule => {
mainValidationRulesMap[rule.key] = [rule]; // ok
});
Upvotes: 1