For example a string that can only be two characters long, which could be used for an ISO country code.
I have used Google and looked through the documentation but cannot find the answer.
Actually it's possible to do
// tail-end recursive approach: returns the type itself to reuse stack of previous call
type LengthOfString<
S extends string,
Acc extends 0[] = []
> = S extends `${string}${infer $Rest}`
? LengthOfString<$Rest, [...Acc, 0]>
: Acc["length"];
type IsStringOfLength<S extends string, Length extends number> = LengthOfString<S> extends Length ? true : false
type ValidExample = IsStringOfLength<'json', 4>
type InvalidExapmple = IsStringOfLength<'xml', 4>
thanks to
function foo (someString: LengthOfString<20>) { // foo takes a string of up to 20 characters?There isn't a way to specify a string length as a type.
If you have a finite list of country codes, you could write a string literal type:
type CountryCode = 'US' | 'CN' | 'CA' | ...(lots of others);
You can do this for strings & arrays.
type FixedString<N extends number> = { 0: string, length: N } & string;
type FixedArray<N extends number, T> = { 0: T, length: N } & T[];
Playground: https://tsplay.dev/m3VQkN
String creation requires that you coerce the type, since the string value's length isn't known at assignment. You can bypass that with as coercion and/or a factory function for that type.
However, array creation (oddly) can assert the array's length on assignment. So that's nice :)
The playground link (above) also includes the beginnings of some length-checker types.
Another option is to use template literals and generate all the possible options. playground
type Letter = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | "w" | "x" | "y" | "z"
type UppercaseLetter = Uppercase<Letter>
type TwoLetters = `${Letter}${Letter}`
type TwoCapitalLetters = `${UppercaseLetter}${UppercaseLetter}`
const x1: TwoLetters = 'abc' // ✗
const x2: TwoLetters = 'ab' // ✔
const y1: TwoCapitalLetters = 'ABC' // ✗
const y2: TwoCapitalLetters = 'AB' // ✔
declare function iso(iso: TwoLetters): any
iso('abc') // ✗
iso('ab') // ✔
anyYou can achieve this using a type constructor and a phantom type which are some interesting techniques to learn about. You can see my answer to a similar question here
Let's go a bit fancy, even with a custom Error type. playground
declare function iso<S extends string>(iso: StringOfLength<S, 2>): any
iso("xyz") // ✗ 'StringLengthError_ExpectedLength<2>'
iso("cz") // ✔
type StringOfLength<S extends string, L extends number> = Length<S> extends L ? S : StringLengthError_ExpectedLength<L>
type Split<S extends string> = S extends `${infer U}${infer V}` ? [U, ...Split<V>] : []
type Length<S extends string> = Split<S>['length']
type StringLengthError_ExpectedLength<L extends number> = { string_length_must_be: L }
And here (playground) is another experiment using custom character sets.
type Hex = "0123456789ABCDEFabcdef"
type ColorSet = ["#", Hex, Hex, Hex]
type X = Validate<'#ff0', ColorSet> // 🗸
type Y = Validate<'#ffx', ColorSet> // ✗ - received "x" expected "012345..."