简体   繁体   中英

TypeScript failing to correctly infer types from generic using keyof as property

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:

  1. Each validation rule ( 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 ).
  2. For ease of working with the validator, at runtime the array of validation rules will be assembled into an object mapping each field ( 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:

Image of 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
});

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
});

Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM