Home > Back-end >  TS - lost type information on filtered keys in curried function
TS - lost type information on filtered keys in curried function

Time:03-09

I have a function that takes an object and set of keys that construct new type by picking by those keys.

I works fine when both parameters are defined in same function, but not when I try to make this function into function that takes only object parameter and returns function that takes in the keys.

I prepared test function that illustrate the issue:

const test = {
  first: '',
  second: '',
  third: '',
};

export function fromObjectWithKeys<T extends { [key: string]: unknown }, K extends keyof T>(obj: T, ...keys: K[]) {
  return (a: K) => a;
}

const res = fromObjectWithKeys(test, 'first', 'third')
//here type of res is: (a: ("first" | "third")) => ("first" | "third")

This one works as expected with second key not being present, next example however fails, losing information about filtered keys:

export function fromObject<T extends { [key: string]: unknown }, K extends keyof T>(obj: T) {
  return (...keys: K[]) => {
    return (a: K) => a;
  };
}

const withKeys = fromObject(test);
const resFromKeys = withKeys('first', 'third');
// here resFromKeys type is: (a: ("first" | "third" | "second")) => ("first" | "third" | "second")

In second example information was lost when part of function was manually curried. How was type information lost? How can I fix it so I am able to have second function accept only one parameter while preserving types down in the hierarchy?

CodePudding user response:

Make the function that's returned generic, moving K from the outer function to that function:

export function fromObject<T extends { [key: string]: unknown }>(obj: T) {
  return <K extends keyof T>(...keys: K[]) => {
    return (a: K) => a;
  };
}

Then you get the same result as previously:

const withKeys = fromObject(test);
const resFromKeys = withKeys('first', 'third');
// here resFromKeys type is: (a: ("first" | "third")) => ("first" | "third")

Playground link

(Thank you spender for pointing out that we could move K rather than using a new generic!)

  • Related