Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specifying the return type based on optional parameters

Tags:

typescript

function f<T>(defaultValue?: T) { return defaultValue; }

const definitelyUndefined = f<string>();      // type: string | undefined
const definitelyString = f<string>('foobar'); // type: string | undefined

Is it possible to define f() such that definitelyUndefined is implicitly undefined and definitelyString is implicitly string?

Background

My real world use case is a function I'm working with and want to improve. It is function f<T>(o: { [key: string]: T }, key: string, defaultValue?: T) and it returns key[o] if it exists, otherwise defaultValue. When I provide it a defaultValue, I'm guaranteed to get T back, but Typescript considers it T | undefined.

like image 419
Mark Avatar asked Jun 11 '26 22:06

Mark


1 Answers

For your first case, I immediately thought of using an overload, for example:

function f<T>(): undefined;
function f<T>(value: T): T;
function f<T>(value?: T) { return value; }

const definitelyUndefined = f();      // type: undefined
const definitelyString = f('foobar'); // type: "foobar"

However, for the more complex use case, I think perhaps you can solve it with overloads and more complex generics, for example:

function f<T, K extends string & keyof T>(o: T, key: K, defaultValue?: T[K]): T[K];
function f<T, V = undefined>(o: T, key: string, defaultValue?: V): V;
function f<T, V = undefined>(o: T, key: string, defaultValue?: V) {
  return o[key] || defaultValue;
}

const obj = { foo: "123" };

const definitelyUndefined = f(obj, "bar");    // type: undefined
const definitelyNumber = f(obj, "bar", 123);  // type: number
const definitelyString = f(obj, "foo");       // type: string

I don't know if this will work for every conceivable scenario (because the return type is determined based on the generic type argument, not the actual function argument), but I think it gets pretty close.

like image 148
p.s.w.g Avatar answered Jun 13 '26 15:06

p.s.w.g