Typescript: constrain argument of function to be a key of an object associated with a value of a particular type

孤者浪人 提交于 2019-12-02 02:25:46

问题


Is there a way to make the following type check?

function getNumberFromObject<T>(obj: T, key: keyof T): number {
  return obj[key] // ERROR: obj[key] might not be a number
}

I want to specify that key should not only be a key of T, but a key with a number value.


回答1:


The most straightforward way to do this so that both the callers and the implementation of getNumberFromObject<T> type check correctly is this:

function getNumberFromObject<T extends Record<K, number>, K extends keyof any>(
  obj: T, 
  key: K
): number {
  return obj[key] // okay
}

And when you call it:

getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "dog"); // okay
getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "cat"); // error
getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "moose"); // okay
getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "squirrel"); // error

That all works well, except that the errors you get are a little obscure in that it complains that Object literal may only specify known properties, and 'dog' does not exist in type 'Record<"somebadkey", number>'. This complaint is an excess property check and isn't really the issue.

If you want to make it so that callers get a better error, you could use a more complicated conditional type like this:

function getNumberFromObject<T, K extends keyof any & {
  [K in keyof T]: T[K] extends number ? K : never
}[keyof T]>(
  obj: T,
  key: K
): T[K] {
  return obj[key] // okay
}

getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "dog"); // okay
getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "cat"); // error
getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "moose"); // okay
getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "squirrel"); // error

In this case, T is unconstrained, but K is forced to be just those keys from T where T[K] is a number.

Now the error says Argument of type '"somebadkey"' is not assignable to parameter of type '"dog" | "moose"'., which is more developer-friendly. Not sure if the extra complexity of signature is worth it to you, though.

Hope that helps. Good luck!


Update: The latter function returns T[K], not number. That could be a good thing, since T[K] is possibly more specific than number. For example:

interface Car {
  make: string,
  model: string,
  horsepower: number,
  wheels: 4
}
declare const car: Car;
const four = getNumberFromObject(car, 'wheels'); // 4, not number

The value four is of type 4, which is more specific than number. If you really want to widen the return type of the function to number, you can... although the implementation will balk at that since the compiler isn't smart enough to realize that T[K] is assignable to number in the generic case. There are ways to deal with that, but the easiest is to use a type assertion in the implementation (return obj[key] as any as number).



来源:https://stackoverflow.com/questions/52188399/typescript-constrain-argument-of-function-to-be-a-key-of-an-object-associated-w

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!