I am trying to make a function in typescript where the second, optional argument, is a key of the first argument. Without the optional argument, the function I want looks like
function getVal<T>(obj: T, key: keyof T) {
return obj[key];
}
However, I would like key
to be optional and take the default value of "id"
. But, the function
function getValBad<T>(obj: T, key: keyof T = "id") {
return obj[key];
}
doesn't typecheck, since typescript doesn't know if T
has a key of id
. A partial fix to this problem is to write
function getValOk<T extends { id: any }>(obj: T, key: keyof T = "id") {
return obj[key];
}
however, this forces T
to always have a key of id
.
My question is, can I write a function getValGood
so that getValGood({id: 1})
typechecks, getValGood({ID: 1}, "ID")
typechecks and getValGood({ID: 1})
doesn't type check. If so, how do I represent getValGood
in typescript?
First, you'll probably want to use another generic argument to type the returned value properly. (Otherwise, obj[key]
will return a union of all possible values on the object, not just the type at key
)
You can overload the getVal
function to take either an object and a key that's a property of the object (2 generics), OR use only a single generic { id: V }
and return something of type V:
type GetVal = {
<T, K extends keyof T>(obj: T, key: K): T[K];
<V>(obj: { id: V }): V;
};
const getVal: GetVal = (obj: Record<string, unknown>, key = 'id') => {
return obj[key];
};
const result1 = getVal({ foo: 'foo' }, 'foo');
const result2 = getVal({ foo: 'foo' }, 'doesntexist'); // Fails
const result3 = getVal({ id: 'val' });
const result4 = getVal({ }); // Fails