Zheeeng
Zheeeng

Reputation: 682

Defines a factory for overload function in TypeScript

Given a situation that wrapping the JSON.stringify with my own function:

declare function stringify(
    value: any,
    replacer?: (key: string, value: any) => any,
    space?: string | number
): string;

declare function stringify(
    value: any,
    replacer?: (number | string)[] | null,
    space?: string | number
): string;


function myStringify(
    data: object,
    replacer: ((key: string, value: any) => any) | (number | string)[] | null,
    space: string | number,
) {
    return stringify(data, replacer, space); // TS error: type is compatible!
}

How to create my own method myStringify reuse the JSON.stringify?

You can check the error detail through the TS playground

Upvotes: 2

Views: 560

Answers (2)

Zheeeng
Zheeeng

Reputation: 682

Inspired by @Titian Cernicova-Dragomir's answer, I found a generic solution for reference. Try the way myFun2 does.

declare function s(r: () => string): string;
declare function s(r: () => number): number;

const a = s(() => '123')  // sring
const b = s(() => 123)  // number

function myFun(r: (() => string) | (() => number)) {
    type R = ReturnType<typeof r> extends string ? string : number

    return s(r)// TS error: type is compatible!
}

const c = myFun(() => '123') // string, is decide by the order we declare the function 's'
const d = myFun(() => 123) // string, totally wrong


function myFun2(r: () => string): string;
function myFun2(r: () => number): number;
function myFun2(r: (() => string) | (() => number)): string | number {
    type R = ReturnType<typeof r> extends string ? string : number

    return s(r as any) as any as R
}

const e = myFun2(() => '123')
const f = myFun2(() => 123)

Upvotes: 0

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

The problem is that since replacer is a union of the types of the replacer parameter from all stringify overloads it is in fact compatible with none of the overloads. When selecting the overload typescript will try to find the overload that best matches the parameters, since your replacer is compatible with neither the first overload (that overload expects a function, your parameter can also be an array) or the second overload (that overload expects an array, your parameter can be a function) the overload resolution process will fail.

You can add the two overloads yourself, or you can use a type guard to essentially call the same function, or you can just use an assertion:

// assert to any
function myStringify(
    data: object,
    replacer: ((key: string, value: any) => any) | (number | string)[] | null,
    space: string | number,
) {
    return JSON.stringify(data, replacer as any, space)
}

// use a type gurad, but it seems overkill to do so.
function myStringify(
    data: object,
    replacer: ((key: string, value: any) => any) | (number | string)[] | null,
    space: string | number,
) {

    if(Array.isArray(replacer)) {
        return JSON.stringify(data, replacer, space);
    } else if(typeof replacer === "function") {
        return JSON.stringify(data, replacer, space);
    }
}

Upvotes: 1

Related Questions