15

An example of what I am trying to achieve:

class Test {
    private _folderId: number;
    private _pageId: number;
    private _folderName: string;
    private _pageName: string;

    constructor(pageId: string | number, folderId: string | number){
        this._folderId = (!isNaN(+folderId)) ? folderId : undefined;
        this._pageId = (!isNaN(+pageId)) ? pageId : undefined;
        this._folderName = (isNaN(+folderId)) ? folderId : undefined;
        this._pageName = (isNaN(+pageId)) ? pageId : undefined;
    }
}

Unfortunately this throws compiler error:

TS2322:Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'.

and so on (similar error for each var).

Is there any way around it? At the moment only thing I can do is to set page and folder id to type any...

Tomas
  • 2,676
  • 5
  • 41
  • 51
  • 1
    you need to define the fields like this `private _pageId: string | number;` and cast the value – iberbeu Sep 13 '16 at 10:44
  • @iberbeu but that would brake the logic wouldnt it? I dont want pageId to be `string | number` , I want it to be only `number` – Tomas Sep 13 '16 at 10:47
  • 1
    Then you need to convert it to a number somehow. If you can take in a `string | number` you either want to `parseInt` the string value (you'll need to check the type at runtime or `.toString()`) or you don't actually want to take in `string | number`. – Dan Sep 13 '16 at 10:49
  • Replace `isNaN` by a type guard. https://www.typescriptlang.org/docs/handbook/advanced-types.html – Paleo Sep 13 '16 at 13:05
  • That's a bad idea. `parseInt` will return `NaN` for empty string values and TypeScript does not know at compile time whether or not `parseInt` will return `NaN`. There needs to be a runtime check done. A type guard will not help there (in fact, it will make things worse) – Dan Sep 13 '16 at 13:31
  • @DanPantry I added an answer with a type guard. – Paleo Sep 13 '16 at 14:05
  • I've deleted my answer after misunderstanding OP's question. OP, please do not try and do this, this is a very stupid idea. `name` and `id` should not be shoved into the same parameter if they are different fields on the result object, unless you have some kind of abstraction on every method to work with both of them. Have a constructor parameter for each argument; if it's optional, allow users to specify null or use an object for arguments. – Dan Sep 13 '16 at 14:10
  • @DanPantry I wouldnt really say its a "very stupid idea" . Once you dealing with URLs, sharing etc etc, you will come to a point when `id` can be either name or number. You need to handle both cases, and basically what you do is if you get name, first thing you do is convert it to id, and then continue. Its a hack, but then what isnt in web development... https://news.ycombinator.com/item?id=12477190 – Tomas Sep 14 '16 at 09:03
  • That article is completely wrong, sorry. And this sort of thing should be handled in your route handler with regular expressions, it should never reach the domain layer – Dan Sep 14 '16 at 09:07
  • @DanPantry and what if this is the function for "domain handler"? Anyway this discussion is turning into opinions, I got my answer no need to talk about it any further – Tomas Sep 14 '16 at 09:20
  • You got your answer and if you presented that code to me in a Code Review, I'd immediately reject it. It's frustrating that StackOverflow is about getting the quickest answer you can copy paste instead of getting the answer that tells you how to write things in a way that won't make you shake your head in 6 months. – Dan Sep 14 '16 at 09:40

4 Answers4

11

The comment under the Guenter Guckelsberger's answer suggests you need to use strings like '123' as numbers. Here is a solution:

/**
* See http://stackoverflow.com/questions/9716468/is-there-any-function-like-isnumeric-in-javascript-to-validate-numbers
*/
function isNumeric(n: any) : n is number | string {
    return !isNaN(parseFloat(n)) && isFinite(n);
}

function intVal(n: number | string): number {
    return typeof n === "number" ? n : parseInt(n, 10);
}

class Test {
    private _folderId: number;
    private _pageId: number;
    private _folderName: string;
    private _pageName: string;

    constructor(pageId: string | number, folderId: string | number) {
        if (isNumeric(folderId))
            this._folderId = intVal(folderId);
        else
            this._folderName = <string>folderId;
        if (isNumeric(pageId))
            this._pageId = intVal(pageId);
        else
            this._pageName = <string>pageId;
    }
}
Paleo
  • 21,831
  • 4
  • 65
  • 76
6

You should use the typeof operator like

typeof folderId === "number"

to check for number or string.

4

Since you have mentioned an ambiguous type for pageId and folderId typescript cannot make out whether your variable is a number or a string at the time of assignment. Hence you need to specify the exact type of your variable at the time of assignment. This can be done using typecasting

You can typecast your folderId and pageId to number or string as follows:

constructor(pageId: string | number, folderId: string | number){
    this._folderId = (!isNaN(+folderId)) ? folderId as number : undefined;
    this._pageId = (!isNaN(+pageId)) ? pageId as number : undefined;
    this._folderName = (isNaN(+folderId)) ? folderId as string : undefined;
    this._pageName = (isNaN(+pageId)) ? pageId as string: undefined;
}

Another way of typecasting would be <number>folderId <string>folderId. You can also go for type Guards as shown in 'Paleo's answer

Nahush Farkande
  • 5,290
  • 3
  • 25
  • 35
  • This is exactly what I wanted, I am sorry I am accepting Paleo's answer, only because I think that `var` will be more obvious for other developers. Thanks! – Tomas Sep 14 '16 at 09:05
  • Like you, I began to typecast (with a type guard) but it's not correct. With your solution, string values like `'123'` will be stored in variables of type `number`. – Paleo Sep 14 '16 at 09:05
  • Yes var does make more sense. But in case you are coding in a `.jsx` file, the editors will complain if you use var, which is where `var as type` comes in handy – Nahush Farkande Sep 14 '16 at 09:32
  • @Paleo not really if you try to assing a string o a variable of type number , the typescript compiler complains with an error along the lines of : `Neither type number nor the type string are assignable to each other` – Nahush Farkande Sep 14 '16 at 09:34
  • @NahushFarkande Call your constructor with `('123', '456')`. `!isNaN(+'123')` returns `true`. Then the string value `'123'` is assigned to the variable `this._folderId` of type `number`. – Paleo Sep 14 '16 at 10:14
  • Oh right.. Well in the end the generated code is javascript which has no concept of types. So I guess that would be the expected behaviour – Nahush Farkande Sep 14 '16 at 12:00
0

For people, like me, who came here looking for a way to constrain a type to strings which can be evaluated to numbers, you can use template literal types. There is a typescript playground with the following:

type SoN = `${number}`
const pass1 = '1'
const pass2 = '-1'
const pass3 = '1.0'
const pass4 = '3e44'
const fail1 = 'abc'
const fail2 = 'NaN'

function sumStrings(str1: SoN, str2: SoN, str3: SoN, str4: SoN, str5: SoN, str6: SoN) {
    return str1 + str2 + str3 + str4 + str5 + str6 // LOL
}

sumStrings(
    pass1,
    pass2,
    pass3,
    pass4,
    fail1,
    fail2
)

There is some discussion of this in the Typescript github issues:

AlexJeffcott
  • 116
  • 2
  • 5