TypeScript: Accept all Object keys that map to a specific type

后端 未结 1 1983
天涯浪人
天涯浪人 2020-12-06 15:06

Given an object type (or class type), I want to write a function that accepts the object and a list of its keys. However, I only want to allow keys that map to a value of a

相关标签:
1条回答
  • 2020-12-06 16:03

    If you want something that works from both the caller's point of view and from the implementer's point of view, you can do this:

    function shouldOnlyAcceptStringValues<K extends PropertyKey>(
      o: Record<K, string>, key: K
    ) {
        const okay: string = o[key];
    }
    

    This is sort of looking at your constraint backwards; instead of constraining key to be the right keys from obj, you are constraining obj to be an object whose value type at key is a string. You can see that okay is accepted as a string, and things work from the caller's side also:

    shouldOnlyAcceptStringValues(obj, "a"); // error!
    // ------------------------> ~~~
    // Argument of type '{ a: number; b: string; c: string; }' is 
    // not assignable to parameter of type 'Record<"a", string>'.
    
    shouldOnlyAcceptStringValues(obj, "b"); // okay
    shouldOnlyAcceptStringValues(obj, "c"); // okay
    

    The only snag is that the error on the first call is probably not on the argument you expect; it's complaining about obj and not "a". If that's okay, great. If not, then you could change the call signature to be the sort of constraint you're talking about:


    type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T]
    function shouldOnlyAcceptStringValues2<T>(o: T, key: KeysMatching<T, string>): void;
    function shouldOnlyAcceptStringValues2<K extends PropertyKey>(
      o: Record<K, string>, key: K
    ) {
        const okay: string = o[key];
    }
    

    The KeysMatching<T, V> type function takes a type T and returns just those keys whose values are assignable to V. And so the call signature will specify T for o and KeysMatching<T, string> for key. Note how I've written that call signature as a single overload and the implementation signature is the same as before. If you don't do that then the compiler is unable to understand that for generic T that T[KeysMatching<T, string>] is assignable to string; it's a higher-order type inference the compiler can't make:

    function shouldOnlyAcceptStringValuesOops<T>(o: T, key: KeysMatching<T, string>) {
        const oops: string = o[key]; // error!
        // -> ~~~~
        // Type 'T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]' 
        // is not assignable to type 'string'.                                                                     
    0 讨论(0)
提交回复
热议问题