Home > Enterprise >  How to strongly type string to custom data type
How to strongly type string to custom data type

Time:11-11

I use strings to represent my data and pass it around. Before using it I have to parse it and get the data out of string. I don't want to check for parse errors on runtime.

How can I give a special type to strings only produced by factory functions and make sure they fail on any other string types.

type MyData = string

let i = 0
function gen_id(): MyData {
    return `hello__${i}`
}

function hello(_: MyData) {
    console.log(_.split('__')[1])
}

hello(gen_id())

hello('world') // I want this to give compile error

Currently this doesn't produce an compile error.

CodePudding user response:

TypeScript has support for string template literals:

type MyData = `${string}__${number}`

let i = 0
function gen_id(): MyData {
    return `hello__${i}`
}

function hello(_: MyData) {
    console.log(_.split('__')[1])
}

hello(gen_id())

hello('world') // Argument of type '"world"' is not assignable to parameter of type '`${string}__${number}`'.(2345)

tsplayground

CodePudding user response:

How can I give a special type to strings only produced by factory functions and make sure they fail on any other string types.

You want a string that's a special string, that only comes from a factory function. You are describing type branding.

You make a branded type by intersecting it without something that should only be knowable to the type itself.

A brand may look like:

const idBrand = Symbol('idBrand')
type MyData = string & { [idBrand]: true }

// or: string & { __brand: 'fancystring' }
// or: string & { brand: idBrand }
// point is string & some spicy sprinkle.

// I like symbols as keys since they can't clash with any existing properties
// and you don't want them in autocomplete suggestions.

const brandedStringA: MyData = 'asdf' // error
const brandedStringB: MyData = 'asdf' as MyData // fine

With that you can make a factory like:

let i = 0
function gen_id(): MyData {
    return `hello__${i}` as MyData
}

Which works as you expect:

function hello(_: MyData) {
    console.log(_.split('__')[1])
}

hello(gen_id()) // fine

hello('world') // error
hello('hello__123') // error

A value of type MyData can still be used anywhere that a string can, but you can't pass a plain string to something that needs a MyData. It must come from the factory function for that.

See Playground

  • Related