2

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

2 Answers 2

1

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);
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Solution two seems not generic, if replacer has two difference function signature, we can't handle it like that. And brings over writings.
Solution one has a potenial propblem that if these two overloads have diffierent return type, we lost the accurate return type.
@Zheeeng Solution 1 does in deed go around type safety and could potentially break. Solution 2 could have problems if the union would contain more functions, but you can't type guard for specific function signatures, fortunately for this case there is only one.
0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.