Search code examples
typescriptnarrowingtype-narrowing

How to narrow arguments with a union type?


I have an argument that is number | string, and try to derive a variable that is a number.
The following code shows what I expected to work. (That kind of thing would work in Python.)

function kg_to_lbs(arg: number | string) {
    let is_number = typeof arg === 'number';
    let arg_number: number = is_number ? arg : parseInt(arg);  // two errors
    return arg_number * 2.20462;
}

To my surprise TypeScript is so strict about types, that it does not allow my solution:

The argument passed to parseInt is not trusted to be a string:   (error TS2345)
Argument of type 'string | number' is not assignable to parameter of type 'string'.

And the result assigned to arg_number is not trusted to be a number:   (error TS2322)
Type 'string | number' is not assignable to type 'number'.

Is there a usual way to do this in TS?
Or is there a way to tell TS, that I am sure what type something is?


Solution

  • The usual way is to inline the condition:

    function kg_to_lbs(arg: number | string) {
        let arg_number: number = typeof arg === "number" ? arg : parseInt(arg);
        return arg_number * 2.20462;
    }
    

    I wonder if there is some trick to still write it in two lines

    You could use a custom type predicate, but these must be functions:

    function isNumber(arg: any): arg is number {
      return typeof arg === 'number';
    }
    
    function kg_to_lbs(arg: number | string) {
        let arg_number: number = isNumber(arg) ? arg : parseInt(arg);
        return arg_number * 2.20462;
    }
    

    BTW, in this particular case, you could have simply written:

    function kg_to_lbs(arg: number | string) {
        return arg * 2.20462;
    }
    

    since the * operator converts its operands to numbers, and converting a string to a number parses it :-)