22

I wrote small function for better handling with types.

function evaluate(variable: any, type: string): any {
    switch (type)
    {
        case 'string': return String(variable);
        case 'number': return isNumber(variable) ? Number(variable) : -1;
        case 'boolean': {
            if (typeof variable === 'boolean')
                return variable;

            if (typeof variable === 'string')
                return (<string>variable).toLowerCase() === 'true';

            if (typeof variable === 'number')
                return variable !== 0;

            return false;
        }
        default: return null;
    }
}

function isNumber(n: any): boolean {
    return !isNaN(parseFloat(n)) && isFinite(n);
}

I try same with generics, but don't know how to get type from generic parameter. It´s possible?

Matěj Pokorný
  • 16,977
  • 5
  • 39
  • 48
  • Not it is not. TypeScript's typing is only a compile-time feature. The output JavaScript has no type information and has no reflection facilities. You have to rely on the good old ``typeof`` in your code as in JavaScript. – Stephen Chung Aug 15 '13 at 05:00
  • 1
    For the record you can collapse your handler for bools to `return !!variable;` – Peter Wone Mar 16 '17 at 00:33
  • @PeterWone That changes the behavior, currently only `"true"` (case insensitive) would result in true, but `!!variable` would result in true for all non-empty strings. You could apply !! to the two non-string branches though if you wanted to. – Mingwei Samuel Jul 04 '20 at 18:55

2 Answers2

39

You cannot eliminate the type string, but you can make your function a lot more intelligent and usable in regards to types by adding overloads:

function evaluate(variable: any, type: 'string'): string;
function evaluate(variable: any, type: 'number'): number;
function evaluate(variable: any, type: 'boolean'): boolean;
function evaluate(variable: any, type: string): unknown {
    ...
    default: throw Error('unknown type');
}
const myBool = evaluate('TRUE', 'boolean'); // myBool: boolean
const myNumber = evaluate('91823', 'number'); // myBool: boolean
evaluate('91823', 'qwejrk' as any); // RUNTIME ERROR (violated types)

const mysteryType = 'number' as 'boolean' | 'number';
const myMystery = evaluate('91823', mysteryType); // COMPILER ERROR, no overload matches.

Playground Link

Note that there is no longer a null case, since it is impossible to know if an unknown string type might actually be containing a valid value like 'number' at compile-time.

This will be good enough for most people.


However...

Note above that the mysteryType union does not work. If you really really really want that to work for some reason, you can use conditional types instead:

function evaluate<T extends string>(variable: any, type: T):
    T extends 'string' ? string :
    T extends 'number' ? number :
    T extends 'boolean' ? boolean :
    never;
function evaluate(variable: any, type: string): unknown {
    ...
    default: throw Error('unknown type');
}
const mysteryType = 'number' as 'boolean' | 'number';
const myMystery = evaluate('91823', mysteryType); // myMystery: number | boolean

Playground Link


Aditionally, if you Googled this question and are wondering how to get T from MyClass<T>, that is possible as well:

class MyClass<T> {}

type GetMyClassT<C extends MyClass<any>> = C extends MyClass<infer T> ? T : unknown;
const myInstance = new MyClass<"hello">();
let x: GetMyClassT<typeof myInstance>; // x: "hello"

Playground Link

Mingwei Samuel
  • 2,917
  • 1
  • 30
  • 40
  • 10
    > "Aditionally, if you Googled this question and are wondering how to get T from MyClass, that is possible as well" that's why I'm here, thanks! – zaphod1984 Aug 17 '22 at 14:56
22

typeof is a JavaScript operator. It can be used at run time to get the types JavaScript knows about. Generics are a TypeScript concept that helps check the correctness of your code but doesn't exist in the compiled output. So the short answer is no, it's not possible.

But you could do something like this:

class Holder<T> {
    value: T;
    constructor(value: T) {
        this.value = value;
    }
    typeof(): string {
        return typeof this.value;       
    }
}

Try it out.

This works because I'm operating on the value inside Holder, not on the Holder itself.

Mingwei Samuel
  • 2,917
  • 1
  • 30
  • 40
Jeffery Grajkowski
  • 3,982
  • 20
  • 23
  • 1
    return this.value.constructor['name']; // <- seems to work in the typescript playground. Taken from [here](http://stackoverflow.com/questions/13613524/get-an-objects-class-name-at-runtime-in-typescript). Could be issues with minification etc. – John Stephenson Feb 03 '17 at 13:23
  • In theory you can also do: function create(c: {new(): T; }): T { return new c(); } I can't get it work from https://www.typescriptlang.org/docs/handbook/generics.html – titusfx Aug 04 '17 at 08:01
  • 4
    If I pass my own Object such as Car it will alert 'object'; instead of 'Car' – mumair Nov 01 '17 at 13:18
  • 1
    *"`typeof` is a JavaScript operator. "* [`typeof`](https://www.typescriptlang.org/docs/handbook/2/typeof-types.html#handbook-content) in a type context is also a TypeScript operator that gives you the TypeScript type of its operand. I don't know whether it existed in 2013 when this answer was originally written. :-) – T.J. Crowder Apr 04 '22 at 14:45