DMZ
DMZ

Reputation: 23

Typescript - Typing a function with optional argument correctly

Suppose we have the following function:

function test<S, T>(obj: S, prop: keyof S, mapper?: (value: S[keyof S]) => T): S[keyof S] | T {
  return typeof mapper === 'function'
    ? mapper(obj[prop])
    : obj[prop];
}

If then I use it without the mapper argument, the type of the return value is not deduced properly:

const value = test({ a: 'stringValue' }, 'a'); // value is of type "unknown"

But if I provide an identity function as the third parameter, it is deduced correctly:

const value = test({ a: 'stringValue' }, 'a', x => x); // value is of type "string"

How should the test function be typed so when we don't provide the mapper argument, the return value's type is deduced correctly?

Playground link

Upvotes: 2

Views: 58

Answers (2)

tokland
tokland

Reputation: 67850

The usual approach is to help the compiler by moving more information to the generic variables of the function. Does this work for you? (Playground)

function test<
    Obj,
    Prop extends keyof Obj,
    Mapper extends ((value: Obj[Prop]) => any) | undefined
>(
    obj: Obj,
    prop: Prop,
    mapper?: Mapper
): undefined extends Mapper ? Obj[Prop] : ReturnType<NonNullable<Mapper>> {
    return mapper ? mapper(obj[prop]) : obj[prop];
}

const value1 = test({ a: "123" }, "a");

const value2 = test({ a: "123" }, "a", x => parseInt(x));

The optional mapper makes the code harder to write, but I am pretty sure this code can be simplified further.

Upvotes: 1

Matthieu Riegler
Matthieu Riegler

Reputation: 54569

Just use function overloads !

function test<S, T>(obj: S, prop: keyof S): S[keyof S];
function test<S, T>(obj: S, prop: keyof S, mapper: (value: S[keyof S]) => T): T;
function test<S, T>(obj: S, prop: keyof S, mapper?: (value: S[keyof S]) => T): S[keyof S] | T {
    return typeof mapper === 'function'
        ? mapper(obj[prop])
        : obj[prop];
}

const value = test({ a: 'stringValue' }, 'a'); // string

const value2 = test({ a: 'stringValue' }, 'a', x => x);  // string

Playground

Upvotes: 3

Related Questions