Reputation: 10384
I have a case where I know the type of a variable, but TypeScript doesn't. I can't use a type guard because I'm inside a switch-case, and because of a known TypeScript bug it can't be implemented.
This is the scenario:
function handleObject(obj: any) {
let myObj: any;
switch (typeof obj) {
case 'number':
myObj: number = obj;
// use myObj as a number
break;
case 'string':
myObj: string = obj;
// use myObj as a string
break;
//etc...
}
}
This is a simplified example, but there are tens of possible types. Obviously, I can't declare a different variable for each scenario only to assign a different type.
My idea was to declare a lot of variables like myObjString
, myObjNumber
, etc..., but it would sound like Hungarian notation, which I don't like at all.
In the above example, the error that I get is:
'number' only refers to a type, but is being used as a value here.(2693)
Because the variable was already declared.
If instead I use myObj = obj as number
or myObj = <number>obj
it compiles, but myObj
remains any
.
How could I get the variable typed?
Upvotes: 1
Views: 2132
Reputation: 10384
@MatthewLayton's answer is exemplary. I've anyway found a simpler way to force the type of an already declared variable. Using curly brackets around a case
of a switch-case is enough to create a new block-scope:
function handleObject(obj: any) {
switch (typeof obj) {
case 'number': {
const myObj: number = obj;
// use myObj as a number
break;
}
case 'string': {
const myObj: string = obj;
// use myObj as a string
break;
//etc...
}
}
}
Upvotes: 0
Reputation: 42260
You could pass a map of type constructors to functions into your handleObject
function, which applies the Open/Closed Principle because you don't need to modify your handleObject
function whenever you want to support new object types; rather, you just apply the type and the handler function to the map and pass the map to the function.
First, let's define a type that represents a type constructor:
type Ctor<T = unknown> = { new(...args: any[]): T };
We can also define a type to represent the map:
type TypedMap<T> = Map<Ctor<T>, (value: T) => void>;
Now let's refactor the handleObject
function to work with an extensible map:
function handleObject(obj: any, map: TypedMap<any>) {
if (map.has(obj.constructor)) {
const fn = map.get(obj.constructor)!;
fn(obj);
}
}
Let's add a test class:
class Foo { }
Let's create a map that we can pass to our function:
const map: TypedMap<any> = new Map();
map.set(Number, (value: number) => console.log("It's a Number"));
map.set(String, (value: string) => console.log("It's a String"));
map.set(Foo, (value: Foo) => console.log("It's a Foo"));
And finally, let's test that it works:
handleObject("hello", map); // It's a String
handleObject(123, map); // It's a Number
handleObject(new Foo(), map); // It's a Foo
Altogether now:
type Ctor<T = unknown> = { new(...args: any[]): T };
type TypedMap<T> = Map<T, (value: T) => void>;
function handleObject(obj: any, map: TypedMap<any>) {
if (map.has(obj.constructor)) {
const fn = map.get(obj.constructor)!;
fn(obj);
}
}
class Foo {
}
const map: TypedMap<any> = new Map();
map.set(Number, (value: number) => console.log("It's a Number"));
map.set(String, (value: string) => console.log("It's a String"));
map.set(Foo, (value: Foo) => console.log("It's a Foo"));
handleObject("hello", map);
handleObject(123, map);
handleObject(new Foo(), map);
It might be a bit rough around the edges, but it seems to work nicely.
Upvotes: 1