39

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.

6 Answers 6

27

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

Sign up to request clarification or add additional context in comments.

1 Comment

I'm confused: I don't normally use types to test things about my strings, I use them to specify an argument (or other variable's type). How does one use this to type (say) a function argument? In other words, how can I do something like function foo (someString: LengthOfString<20>) { // foo takes a string of up to 20 characters?
22

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);

1 Comment

I thought about doing something like that, but using npmjs.com/package/country-data, but I decided against it because it would be overkill given the fact that the country codes will be returned from a backend API. I just want to ensure the variable cannot contain any obviously wrong data and thought it would be simple to specify length. I also thought about using an array but thought of that as somewhat convoluted plus you apparently cannot specify an enforced max length for an array (stackoverflow.com/questions/41139763/…)
7

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.

Comments

6

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')  // ✔

4 Comments

This is cool, but looks at certain number of combinations TS gives up and makes it type any
@MaximMazurok Then you need to use a function and something like this other answer.
Yeah, I wanted to have type of string with limited set of letters. But it's probably an overkill anyway, so ended up just using string type
@MaximMazurok I added another playground to the other linked answer above that uses custom character sets, although it has very limited usability. Mostly a fun experiment. The TS playground link is too long for a comment, so I had to include it in an answer.
5

You 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

Comments

3

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..."

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.