TIL: String type with a specific length in TypeScript

We’ll declare a Phantom Type:

type StringN<N> = string & {
  length: N;
  readonly X: unique symbol;
}

const isStringN = <N extends number>(length: N, s: string):
  s is StringN<N> => s.length == length;

export const stringN = <N extends number>(length: N, s: string): StringN<N> => {
  if (!isStringN(length, s)) throw new Error(`input must have length ${length}`);
  return s;
}

Then we can use it as below:

export type MicroUuid = StringN<4>;
export const microUuid = () => stringN(4, newUuid().slice(0, 4)) as MicroUuid;

let s4: MicroUuid  = microUuid();     
let s5: StringN<5> = stringN<5>(5, 'hello');

s4 = 'any string'; // invalid
s4 = s5;           // invalid

We can extend it further to make a specific type for our MicroUuid:

type MicroUuid = string & {
  readonly X: unique symbol;
}

const isMicroUuid = (s: string): s is MicroUuid => /[a-f0-9]{4}/.test(s);

const asMicroUuid = (s: string) => {
  if (!isMicroUuid(s)) throw new Error('invalid input');
  return s;
}

const newMicroUuid = () => newUuid().slice(0, 4) as MicroUuid;

We can use the same declaration readonly X: unique symbol in multiple type, and they are still different:

let s5 = stringN<5>(5, 'hello');
let id: MicroUuid = s5;          // invalid

And another trick: Flavoring: Flexible Nominal Typing for TypeScript.

Author

I'm Oliver Nguyen. A software maker working mostly in Go and JavaScript. I enjoy learning and seeing a better version of myself each day. Occasionally spin off new open source projects. Share knowledge and thoughts during my journey. Connect with me on , , , and .

Back Back