This is an interesting question. I can't think of a good scenario where this is happening often enough to warrant a generic function. Most of the time, I would handle it on a case-by-case basis. Typically use typeof
for primitives and duck typing for user-defined types. However this is a good exercise so I'll give it shot.
Your code is performing the typeof
check but then it redundantly passes the variable to a constructor. If typeof obj === 'number'
then you already have a number, no need to "cast". Here is a modified version of the answer given by @mallison above. I also added a Classes to this solution however Interfaces do not work as you might hope.
// Overloaded function
function tryCast(o: number, t: "number"): number;
function tryCast(o: string, t: "string"): string;
function tryCast(o: boolean, t: "boolean"): boolean;
function tryCast(o: IFace, t: "IFace"): IFace;
function tryCast(o: Function, t: "function"): Function;
function tryCast<T>(o: any, t: string): T;
function tryCast<T>(o: T, t: any): T {
if (t === typeof o || (o['constructor'] && t === o['constructor']['name'])) {
return o;
} else {
return null;
}
}
// Usage examples
var s1 = tryCast(70, 'string');
var s2 = tryCast('hello world', 'string');
var b1 = tryCast({b:true}, 'boolean');
var b2 = tryCast(true, 'boolean');
var n1 = tryCast('nan', 'number');
var n2 = tryCast(91, 'number');
var f1 = tryCast(123, 'function');
var f2 = tryCast(function foo() { return 1}, 'function');
class Classy { public sanDeigo = true; }
var c1 = tryCast<Classy>({soFly:false}, 'Classy');
var c2 = tryCast<Classy>(new Classy(), 'Classy');
interface IFace { eyes: number; hasMouth: boolean; }
var i1 = tryCast<IFace>({ eyes: 2, fake: true }, 'IFace');
var i2 = tryCast<IFace>({ eyes: 2, hasMouth: true }, 'IFace');
// Runtime tests
document.write(`
s1:${s1}, // expect null<br/>
s2:${s2}, // expect string<br/>
b1:${b1}, // expect null<br/>
b2:${b2}, // expect boolean<br/>
n1:${n1}, // expect null<br/>
n2:${n2}, // expect number<br/>
f1:${f1}, // expect null<br/>
f2:${f2}, // expect function<br/>
c1:${c1}, // expect null<br/>
c2:${c2}, // expect Classy<br/>
i1:${i1}, // expect null<br/>
i2:${i2}, // expect IFace...but its not!<br/>
`);
// Compiler tests
s1.toUpperCase();
s2.toUpperCase();
b1.valueOf();
b2.valueOf();
n1.toFixed(2);
n2.toFixed(2);
f1.apply(this);
f2.apply(this);
c1.sanDeigo;
c2.sanDeigo;
i1.eyes;
i2.eyes;
If you compile and run the code you'll see the following output:
s1:null, // expect null
s2:hello world, // expect string
b1:null, // expect null
b2:true, // expect boolean
n1:null, // expect null
n2:91, // expect number
f1:null, // expect null
f2:function foo() { return 1; }, // expect function
c1:null, // expect null
c2:[object Object], // expect Classy
i1:null, // expect null
i2:null, // expect IFace...but its not!
So what's going on? There's no general way to cast an interface because it is a compile-time entity. The runtime doesn't know about interfaces. The class works because we know how TypeScript will compile the code but this might not work in the future if the constructor name is not the same as the class name (you might want to check the ES6 spec before using this in production).
Again, the only way to check an interface type at runtime is duck typing as I mentioned earlier. I will leave you with what I think the proper solution to a scenario where you wouldn't know the type at compile time.
$.getJSON('data.json', function(data: IFace) {
if (data && data.eyes > 0 && data.hasMouth) {
// this looks like an IFace to me!
} else {
// data is no good!
}
});