[Typescript] Branded type
For example, we have a type repersent currency: USD
, value should be something like '$123'
, a string type with `$` and number.
So let's say we make a USD type as string
type USD = string
PORS: it is semantice
CONDs: I can give any value doesn't match the requiremenets of USD
for example: '#123'
To solve this problem we can use Branded type.
Define a Branded type
declare const brand: unique symbol;
type Brand<K, T> = T & {[brand]: K}
Now a branded USD
type should be
type USD = Brand<'USD', string>
/*
string & {
brand: 'USD'
}
*/
Now if we assign correct value to a branded USD type, we found it has error:
// @ts-expect-error
const price: USD = '$123' // '$123' is not assignable to USD
Use Branded type with Type guards and Type assertions
declare const brand: unique symbol;
type Brand<K, T> = T & {[brand]: K}
type USD = Brand<'USD', string>
function isUSD(str: string | USD): str is USD {
return typeof str === 'string' && str.toString().startsWith('$') && !Number.isNaN(+str.substring(1))
}
function assertUSD(str: string | USD): asserts str is USD {
const b = typeof str === 'string' && str.toString().startsWith('$') && !Number.isNaN(+str.substring(1))
if(!b) {
throw new Error('Not a USD currency')
}
}
Usage with type guards:
const priceUSD = '$123'
const priceEUR = '€123'
function getUSDPayment(usd: USD) {
console.log(usd)
}
// OK
if (isUSD(priceUSD)) {
getUSDPayment(priceUSD)
}
// NOT
if (isUSD(priceEUR)) {
getUSDPayment(priceEUR)
}
Usage with type assertion:
const priceUSD = '$123'
const priceEUR = '€123'
function payByUSD(usd: string) {
assertUSD(usd)
getUSDPayment(usd)
}
function getUSDPayment(usd: USD) {
console.log(usd)
}
payByUSD(priceUSD)
payByUSD(priceEUR) // throw error
Resources:
Surviving the TypeScript Ecosystem — Part 6: Branding and Type-Tagging
Branded Types give you stronger input validation
https://github.com/kevinbgreene/typescript-demo/blob/branding/src/index.ts