简体   繁体   中英

Typescript: Narrowing down mapped types does not work with generics

The Problem: I want to write a function that takes an object and the name of a property as parameters. What I want to achieve is that only property names of properties with a specific type are accepted.

Example: In a person object I have fields name:string and age:number, my function should then only be able to be called with the parameters (person, 'name'). This can be achieved by creating this type:

export type OnlyPropertiesWithSpecificType<TYPE, T> = {
  [K in keyof T]: T[K] extends TYPE ? K : never;
}[keyof T];

When accessing the property inside the function the type of the property's values should be constrainted like this:

type Person = {
  name: string;
  age: number;
};

function somePersonFunction(obj: Person, param: OnlyPropertiesWithSpecificType<string, Person>): string {
  return obj[param]; // works, obj[param] is of type 'string'
}

However when I try to generify the function, it is no longer type constrainted:

function someGenericFunction<T>(obj: T, param: OnlyPropertiesWithSpecificType<string, T>): string {
  return obj[param]; // doesn't work: "TS2322: Type 'T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]' is not assignable to type 'string'."
}

This is confusing, because the compiler still only accepts property names belonging to properties of type 'string' as param:

someGenericFunction(person, 'name'); // works
someGenericFunction(person, 'age'); // doesn't work

What I tried:

  • TS versions 3.4.5 and 4.1.2.
  • Various variations on T, ie T extends object

I created a sandbox with the above example: https://codesandbox.io/s/typescript-forked-ypy0b

How do I solve this problem?

It looks like TS hasn't worked out that someGenericFunction will always return a string , even though in practice it always will.

However unless you absolutely need the : string return type annotation from someGenericFunction , you can just omit it and your code will work as expected.

function someGenericFunction<T>(
  obj: T,
  param: OnlyPropertiesWithSpecificType<string, T>
) {
  return obj[param]; // inferred return type is T[OnlyPropertiesWithSpecificType<string, T>]
}

When called with actual types TS does infer that T[OnlyPropertiesWithSpecificType<string, T>] will always be assignable to string , which is why the functions do work.

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