ITNEXT

ITNEXT is a platform for IT developers & software engineers to share knowledge, connect, collaborate, learn and experience next-gen technologies.

Follow publication

Typing basic types with Typescript

--

So here’s an issue. We have strings and we have ISOStrings and ids .

  1. We don’t want to be able to assign any string to a field that has ISOString but ISOString should be assignable to string
  2. We don’t want an id of one data type to be assignable to an id of another data type.

1: Template literal types

Template literal types have been a part of Typescript since 4.1. Now we can specify a string that fulfills a template.

type Hello<Greeted extends string> = `Hello, ${Greeted}!`
type HelloWorld = Hello<'World'>
const hello1: HelloWorld = 'Hello, World!'
const hello2: HelloWorld = 'Hello, Oli!' // error

This way we can define an ISOString by using ${number} . Be careful though since the resulting type will be a Union type of too many fields if you do it “properly”. Here’s how we would create a type for ISOStrings:

type PaddedNumber = `${'0' | ''}${number}`
type ISODate = `${number}-${PaddedNumber}-${PaddedNumber}`
type ISOTime = `${PaddedNumber}:${PaddedNumber}:${PaddedNumber}`
export type ISOString = `${ISODate}T${ISOTime}Z`

2: Branding

Conventional branding

So far people have used some sort of “Branding” approach to having an id not assignable to another id . For example:

interface UserBrand { type: 'User' }
type UserId = string & UserBrand
interface ArticleBrand { type: 'Article' }
type ArticleId = string & ArticleBrand

now when we…

const userId: UserId = user.id // ok
const articleId: ArticleId = userId // error

…with a great benefit to our type-checking. This approach does cause issues when we do this though.

const userId: UserId = 'user-id' // error
const userId2: UserId = 'user-id' as UserId // casting is ok

Alternative 1: Branding that allows assigning basic types

So here’s a solution from Drew Colthorp’s article on Opaque Types (in his article “Flavoring”) by using this interface we create a union types with string instead that are not inter-assignable

interface OpaqueType<OpaqueT> {
_type?: OpaqueT;
}
export type Opaque<T, OpaqueT> = T & OpaqueType<OpaqueT>;

Now if we change our id types to…

type UserId = Opaque<string, "User”>
type ArticleId = Opaque<number, "Article">

we now get this effect:

const userId: UserId = 'user-id' // ok
const articleId: ArticleId = 'article-id // ok
const userId2: UserId = articleId // error

Using “unique symbol”

To conserve a single source of truth with branding and to make sure that two brands can never be the same using unique symbol can handle those cases. As an example we can create two ISOString brands like so:

type ISOString = string & {readonly ISOString: unique symbol}
type ISOString2 = string & {readonly ISOString: unique symbol}
let isoString1: ISOString = 'isostring' as ISOString // ok
let isoString2: ISOString2 = 'isostring2' as ISOString2 // ok
isoString1 = isoString2 // error

You can likewise instead of making an Opaque type with string literals you can brand it with a unique symbol instead.

type ISOString = Opaque<string, { readonly T: unique symbol }>
type ISOString2 = Opaque<string, { readonly T: unique symbol }>
const isostring: ISOString = 'isostring'
let alsoIsostring: ISOString2 = 'isostring'
alsoIsostring = isostring // error

The difference is subtle. Let’s say two people create the same brand with the same signature then with unique symbol they’ll will not be interchangeable. The bigger the codebase the more you’ll have to know that you’re importing the correct CommentId or type for example.

When should you use which?

That, of course, depends on your needs.

  • If you want a built-in string validation then use a Template Literal
  • If you want to make sure that people don’t mix ids or strings of different formats then use some sort of branding.
  • If you need both, then use both.

And that’s all for now, happy coding!

Appendix: Book reccomendations

These books have allowed me to leapfrog a couple of years of experience, and it could do the same for you.
* Clean Code by Robert C. Martin
* Clean Architecture by Robert C. Martin

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Published in ITNEXT

ITNEXT is a platform for IT developers & software engineers to share knowledge, connect, collaborate, learn and experience next-gen technologies.

Written by Olafur Palsson

Software Engineer @ Cubus ehf. Reykjavík, Iceland

No responses yet

Write a response