'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.



Solution 1:[1]

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Titian Cernicova-Dragomir