I got this to work using a mapped type that only keeps number
and number | undefined
properties:
interface Payload {
value1: number;
value2: string;
value3?: number;
value4?: string;
}
type NumberOnly<T> = {
// Doesn't allow `number | undefined` if `strictNullChecks` is enabled
//[key in keyof T as T[key] extends number ? key : never]: T[key];
// This will always allow `number | undefined` on top of just `number`
[key in keyof T as T[key] extends number | undefined ? key : never]: T[key];
};
function F<T extends NumberOnly<any>, K extends keyof NumberOnly<T>>(arr: T[], property: K) {
const value = arr[0][property];
return value;
}
function foo(payloads: Payload[]) {
F(payloads, "value1"); // should work
F(payloads, "value2"); // should not work because value2 is a string
F(payloads, "value3"); // should work (you can filter undefined if you want)
F(payloads, "value4"); // should not work because value4 is a string
F(payloads, "value5"); // should not work because value5 not in Payload[]
}
technically it would also accept value5: undefined
but not e.g. value5: string | undefined
, an edge case that shouldn't really matter.
It only accepts value1
and value3
:

based on this answer making use of a TypeScript 4.1 feature