2

Given the following interface:

interface Payload {
  value1: number;
  value2: string;
}

I want to create a function that accepts Payload[] and only the keys of Payload that extend number. Something like this:

function F<T, K extends keyof T, T[K] extends number>(arr: T[], property: K) {
  //
}

function foo(payloads: Payload[]) {
  F(payloads, "value1"); // should work
  F(payloads, "value2"); // should not work because value2 is a string
  F(payloads, "value3"); // should not work because value3 not in Payload[]
}

See playground

Eliya Cohen
  • 10,716
  • 13
  • 59
  • 116

1 Answers1

4

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:

Example result

based on this answer making use of a TypeScript 4.1 feature

Kelvin Schoofs
  • 8,323
  • 1
  • 12
  • 31
  • 1
    I was also using v4.3.5 although without a `tsconfig.json`, and now that I checked, enabling `strictNullChecks` does break `number | undefined`. I'll update my answer (and the screenshot) to fix/mention this, along with fixing your issue of not being able to index. – Kelvin Schoofs Jul 22 '21 at 11:45
  • 1
    Great! The part I couldn't crack was `T extends NumberOnly`. FWIW, [here's a generic version](https://tsplay.dev/mx5B7W). – T.J. Crowder Jul 22 '21 at 12:30