You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
excerpt: 'Adding additional type-safety to distinguish same data-types'
date: '2023-01-01T00:29:32.431Z'
Javascript is inherently not a type-safe language. You don't get compile-time errors when you are doing potentially unsafe operations between incompatible types. Typescript has become a defacto way of improving javascript's shortcomings when it comes to type-safety and has actually made javascript a viable language to build large scale applications.
Let's take an example:
const foo = 5;
const bar = 'five';
...
if (foo !== bar) {
console.log('foo is not bar');
} else {
console.log('foo is bar');
}
With pure javascript, this piece of code compiles, and logs foo is not bar as expected. However, in any non trivial piece of code, the compiler should tell us that this operation is moot, and we might be breaking some assumptions. Let's update thie code with typescript:
const foo: number = 5;
const bar: string = 'five';
...
if (foo !== bar) {
^^^^^^^^^^^ "This comparison appears to be unintentional because the types 'number' and 'string' have no overlap."
console.log('foo is not bar');
} else {
console.log('foo is bar');
}
Here typescript gives us a nice helpful message saying, hey you might be doing something wrong comparing two different types.
But what happens when two variables are of the same data type:
function convertToDate(isoDateString: string): Date {
return new Date(isoString);
}
In this example, convertToDate takes in an isoString and converts it to a date object. This is an extremely common operation in javascript. Many APIs will return json results with iso dates typed as strings and it's the responsibility of the client to convert the value to a Date object if the client needs to use it as a date. The convertToDate function here takes any string as an input which can lead to a false sense of security that the code we're writing is safe. Take this code for example:
Here, our code doesn't provide any type-safety that userInputDateString is in fact an isoDate. It's a string so javascript and typescript are both happy. As you can imagine in a large scale application, if a developer is unaware of how convertToDate works, they may feel the need to perform validation before calling the function to be sure that they are supplying the right type of input. Or if the function throws, then they would need to handle a validation error everywhere that this function is called.
Is it possible for us to differentiate the types of apiDateString and userInputDateString such that we get type-safety? This is where brand types come in. A brand type adds another layer of safety to distinguish vales of the same data type. If we modify the above to something like this:
function convertToDate(isoDateString: ISODateString): Date {
return new Date(isoDateString);
}
const apiDateString = '2023-01-01T00:29:32.431Z' as ISODateString;
let convertedDate = convertToDate(apiDateString); // okay!
const userInputDateString = 'January 1st, 2023';
convertedDate = convertToDate(userInputDateString);
// ^^^^^^^^^^^^^^^^^^^
// Type 'string' is not assignable to type '{ _brand: "ISODateString"; }'
If we could create completely new type safe data types, the above would give us a compile time error. The convertToDate function only accepts ISODateString types. If you (or an api) is providing some guarantee that the value being supplied is an ISODateString type, then the code will compile. Otherwise, it'll throw a compile-time error. This is possible with typescript with branded types. Here is what a generic Brand type looks like for string types:
export type ISODateString = BrandedString<'ISODateString'>;
If a variable is assigned to an ISODateString type then, it's safe to be sent to convertToDate. Of course, you can use narrowing and type-predicates to perform validation from a regular string to assert that the string is in fact a branded string. Once that validation is done, however, we can be confident that the value being passed around is a true ISODateString.
const isISODateString = (input: string): input is ISODateString {
// Really naive way to do validation, lol
return !isNaN((new Date(input)).getTime());
}
if (isISODateString(userInputDateString)) {
const date = convertToDate(userInputDateString) // okay!
}
We use this technique across the codebase at my company. We use it for:
entity ids (there is no point in comparing UserId and CompanyId since they are always a disjointed set).
data that has a unique format for data transmission (like date strings)
metrics (weight metrics should never really be compared against spatial metrics)
In fact, as I was building out the bookmarks page, the API (I use Raindrop.Io) returned a format that resembled fields that contained some of the above types of data. I had to build a type for the json anyway, so my type looks something like this:
excerpt: 'Adding additional type-safety to distinguish same data-types'
date: '2023-01-01T00:29:32.431Z'
Javascript is inherently not a type-safe language. You don't get compile-time errors when you are doing potentially unsafe operations between incompatible types. Typescript has become a defacto way of improving javascript's shortcomings when it comes to type-safety and has actually made javascript a viable language to build large scale applications.
Let's take an example:
With pure javascript, this piece of code compiles, and logs
foo is not bar
as expected. However, in any non trivial piece of code, the compiler should tell us that this operation is moot, and we might be breaking some assumptions. Let's update thie code with typescript:Here typescript gives us a nice helpful message saying, hey you might be doing something wrong comparing two different types.
But what happens when two variables are of the same data type:
In this example,
convertToDate
takes in an isoString and converts it to a date object. This is an extremely common operation in javascript. Many APIs will return json results with iso dates typed as strings and it's the responsibility of the client to convert the value to aDate
object if the client needs to use it as a date. TheconvertToDate
function here takes any string as an input which can lead to a false sense of security that the code we're writing is safe. Take this code for example:Here, our code doesn't provide any type-safety that
userInputDateString
is in fact an isoDate. It's a string so javascript and typescript are both happy. As you can imagine in a large scale application, if a developer is unaware of howconvertToDate
works, they may feel the need to perform validation before calling the function to be sure that they are supplying the right type of input. Or if the function throws, then they would need to handle a validation error everywhere that this function is called.Is it possible for us to differentiate the types of
apiDateString
anduserInputDateString
such that we get type-safety? This is where brand types come in. A brand type adds another layer of safety to distinguish vales of the same data type. If we modify the above to something like this:If we could create completely new type safe data types, the above would give us a compile time error. The
convertToDate
function only acceptsISODateString
types. If you (or an api) is providing some guarantee that the value being supplied is anISODateString
type, then the code will compile. Otherwise, it'll throw a compile-time error. This is possible with typescript with branded types. Here is what a genericBrand
type looks like for string types:For an ISODateString, you may get:
If a variable is assigned to an
ISODateString
type then, it's safe to be sent toconvertToDate
. Of course, you can use narrowing and type-predicates to perform validation from a regular string to assert that the string is in fact a branded string. Once that validation is done, however, we can be confident that the value being passed around is a true ISODateString.We use this technique across the codebase at my company. We use it for:
UserId
andCompanyId
since they are always a disjointed set).In fact, as I was building out the bookmarks page, the API (I use Raindrop.Io) returned a format that resembled fields that contained some of the above types of data. I had to build a type for the json anyway, so my type looks something like this:
Now we can have additional typesafety with the
created
,lastUpdate
,link
and_id
fields. Here is a typescript playground with the above concepts.The text was updated successfully, but these errors were encountered: