My goal here is to create a function named: getFields
. This function has a generic <T>
and a parameter ...fields: Array<keyof T>
. I would like this function to return a function which when given an object of type <T>
will return a reduced object with only the properties named in ...fields
.
The following is one helper type and my getFields
implementation:
type SubObj<T, S extends Array<keyof T>> = Pick<
T,
keyof { [K in S[number]]: K extends keyof T ? K : never }
>;
export function getFields<T extends Record<string, unknown>>(
...fields: Array<keyof T>
): (obj: T) => SubObj<T, typeof fields> {
return (obj: T) =>
Object.fromEntries(fields.map((field) => [field, obj[field]])) as SubObj<
T,
typeof fields
>;
}
I tested this implementation with the following code:
type A = {
a: string;
b: string;
c: string;
};
const b = getFields<A>('a', 'c')({ a: '', b: '', c: '' });
However, when I look at the typeof
b
it is Pick<A, "a" | "b" | "c">
. What I really want is Pick<A, "a" | "c">
.
I have tried a lot of things to make this work the way I intend it to, but the only success was adding a second generic argument which would require me to change the code to this:
const b = getFields<A, ['a','c']>('a', 'c')({ a: '', b: '', c: '' });
This is too redundant for me to see as acceptable.
At this point, I think I've hit the limits of my TypeScript abilities because I can't think of any other way to accomplish what I'm looking for.
Is this even possible to do with TypeScript? If so, what do I need to do?
TypeScript does not currently support partial type parameter inference (see microsoft/TypeScript#26242). If you have multiple type parameters in a type/function, you either need to specify them all explicitly, or let them all be inferred. There is no way to specify one and let the other be inferred. If you want to go that route, there are workarounds, but then this question becomes a duplicate of other such questions, and I can point to, for example, this answer for how to proceed.
Stepping back, though, I'd say that the example code here might possibly be expressed in a simpler way that doesn't require anyone specify any types at all. Consider this:
Here we are completely removing the object type
T
from the call signature ofgetFields()
. AllgetFields()
cares about is getting a list of key-like arguments. It then returns a function which is also generic, and this one cares about the object typeT
, and constrains it to be something with the keys fromK
. BothT
andK
are therefore inferrable:Since the return value of
getFields()
is a generic function, it can be used for types other thanA
, for example:But should still error if you give it something inappropriate:
If you really care about specifying
T
you can do it when calling the return function:If for some reason you really want to specify
T
at the beginning and inferK
, you need to use one of the workarounds for partial type inference, like even more currying:Playground link to code