Jørgen Tvedt
Jørgen Tvedt

Reputation: 1274

Typescript conditional return type based on string argument

When using union of string literals as input argument, what would it take to remove the casts and put the type into the function header:

const get = <T extends "barcode" | "mqtt">(s: T) =>
    s === "barcode" ?
        <T extends "barcode" ? {scan: () => string} : {pan: () => string}>{scan: () => "we are scanning"} :
        <T extends "barcode" ? {scan: () => string} : {pan: () => string}>{pan: () => "we are panning"}

get("barcode").scan() // OK
get("mqtt").pan()     // OK
get("barcode").pan() // Error

I ran into this trying to answer someone else's question: https://stackoverflow.com/a/55059318/2684980.

Upvotes: 9

Views: 7827

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249646

The cleanest solution is such cases (although not any more type safe than the type assertions) is to use overloads instead. You can use conditional types in the public signature, and a simple union in the implementation signature. You will need to switch to a function declaration as function expressions (arrow or regular) don't easily suport overloads:

function get<T extends "barcode" | "mqtt">(s: T): T extends "barcode" ? { scan: () => string } : { pan: () => string }
function get(s: "barcode" | "mqtt"): { scan: () => string } | { pan: () => string } {
    return s === "barcode" ?
        { scan: () => "we are scanning" } :
        { pan: () => "we are panning" }
}

get("barcode").scan() // OK
get("mqtt").pan()     // OK
get("barcode").pan() // Error

Upvotes: 16

Related Questions