Branded Types in Typescript
This is not an “official” feature of Typescript. This is a cool workaround that people use to enforce more specific usage of generic types, such as string or number.
For example, you have this block of code:
type User = {
name: string;
id: string;
}
declare function getHeightOfuser(id: string): number;
// ...
const user = {name: "Johnny", id: "1234abc"}
getHeightOfuser(user.id); // <- probably right
getHeightOfuser(user.name); //<- probably wrong
Even though we can pass any old string to getHeightOfuser
, what we REALLY want is to make sure that we’re sending a user id to that function.
Instead, we can do this:
type UserId = string & {__brand: "User"};
type User = {
id: UserId;
name: string;
}
declare function getHeightOfuser(id: UserId): number;
const user: User = {name: "Johnny", id: "1234abc" as UserId};
getHeightOfuser(user.id);
getHeightOfuser(user.name);
Example on Typescript playground: link
So, we get a way to enforce that ONLY user’s id is passed in. A bit stringent if you ask me but I’m chill with it.
Also, this only works if you NEVER touch the __brand
attribute. You don’t have to define that attribute, but it needs to exist in typescript world.
Alternative Method
Instead of the __brand
property, you can also use a Brand
generic, as shown by Matt Pocock in this tweet thread.
declare const brand: unique symbol;
type Brand<T, TBrand extends string> = T & {[brand]: TBrand};
type Password = Brand<string, "Password">;
const bro = "yoyoyoooo";
const validatePassword = (unsafe: string): unsafe is Password => {
return unsafe.length < 8
}
const writePassword = (safe: Password) => {
// write to db
}
validatePassword(bro)
if(validatePassword(bro)) {
writePassword(bro)
}