The Get Method's Shortcoming - ldco2016/microurb_web_framework GitHub Wiki
Currently, the return type signature of my get()
method is not exactly appropriate:
export class Attributes<T> {
constructor(private data: T) {}
get(propName: string): number | string {
return this.data[propName];
}
set(update: T): void {
Object.assign(this.data, update);
}
}
Right now it's either a number
or a string
I am returning.
I am going to imagine that I decide to put in a limitation inside this application and say we can only ever have attributes that are number | string | boolean
like so:
export class Attributes<T> {
constructor(private data: T) {}
get(propName: string): number | string | boolean {
return this.data[propName];
}
set(update: T): void {
Object.assign(this.data, update);
}
}
So I would have the basic types covered here and maybe I just run with this and say I can only ever store these types of values and my code would work just fine. Well, for the most part, if I went down this route, the following is the code I would have to write anytime I want to access the properties inside this get()
method. Once we see this difficulty, we will understand why we want to get away from this union pattern I currently have.
I am going to create an instance of Attributes
with UserProps
serving as my type and I will look at how awkward it will be to make use of the current get()
method like so:
import { UserProps } from './User';
export class Attributes<T> {
constructor(private data: T) {}
get(propName: string): number | string | boolean {
return this.data[propName];
}
set(update: T): void {
Object.assign(this.data, update);
}
}
const attrs = new Attributes<UserProps>({ id: 5, name: 'asdf', age: 20 });
So now I will try to access id
through attrs
or specifically through the get()
method. To do so, I would have to do the following:
import { UserProps } from './User';
export class Attributes<T> {
constructor(private data: T) {}
get(propName: string): number | string | boolean {
return this.data[propName];
}
set(update: T): void {
Object.assign(this.data, update);
}
}
const attrs = new Attributes<UserProps>({ id: 5, name: 'asdf', age: 20 });
const id = attrs.get('id');
Here is the issue, if I now mouse over id, I get back a type annotation on id
of string | number | boolean
:

And thats because inside the class of Attributes
it says when I call get()
I am going to return one of the possible types:
get(propName: string): number | string | boolean {
return this.data[propName];
}
So before I can access any property on id
that only belong to a number
, I have to set up one of those type guards. If I try to access any properties on id
I will only see the properties that are common between number
, string
, boolean
.
So to treat id
as if it were truly a number
, I have to setup a type guard like so:
if (typeof id === 'number') {
id;
}
If I mouse over id
, I see that now it is a number:

So I would need to repeat this type guard every single time I needed to access a property off of my Attributes
object. This is not a pattern I want to dive into. It would be better to just call attrs.get()
and have whatever value I get back be of the correct type, that would be super ideal.
I want to be able to say this id
here, if I call get()
with that id
I want it to be a number
100% of the time without any additional hassle on my side.
One way I could do this is to put on a direct type annotation like so:
const id: number = attrs.get('id');
if (typeof id === 'number') {
id;
}
But if I do that I am going to see an error:
The error is saying I can only access one of these possible values and I cannot just assume that it is of type number
, so that would not work out.
The other thing I could try doing is using a type assertion. A type assertion is where I try to override TypeScripts behavior and let it know that I know what I am doing and to believe me when I say that id
is a number like so, const id = attrs.get('id') as number;
.
So that right there is the type assertion I am trying to just override TypeScript and tell it whatever I get back from this function call, its going to be a number
, just trust me.
So now when I mouse over id
:
Yep, its a number
, but obviously this approach has a downside. If I ever come back to my code and decide that instead of id
I actually want to get my name
, const name = attrs.get('name') as number;
. Well, name
is definitely not a number
, so if I put out some code like this, TypeScript would think its okay, but when I try to run it, chances are something will not work as expected because I am treating name
which is a string
as though it were a number
. So its clear that all these possible solutions would have, well, some really big downsides.
So how do I just call attrs.get('name')
and have TypeScript understand what I am trying to do.
With just this code here, const name = attrs.get('name');
, I need TypeScript to understand that name
is 100% of the time is going to be a string. Now to make that happen I will have to use some complicated syntax around generics.