30

After going through the docs, it seems there is no direct way to type check for min/max length of a string datatype.

But, is there a way to declare a string datatype using some custom types so that it checks whether the string length is with the given bounds?

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
Deepak
  • 2,487
  • 3
  • 21
  • 27
  • Types are just that: types. And they don't even exist anymore at runtime, which is just JavaScript. A type cann't check that a string has an expected length. You need to write code for that. – JB Nizet Aug 12 '18 at 22:16
  • Depends on the use-case but if this is for database storage, there are plenty of type validation libraries like Mongoose's [Schemas](http://mongoosejs.com/docs/guide.html) that can do this out-of-the-box. If you clarify your use-case, I'm sure you'll get more targeted advice. – Patrick Roberts Aug 12 '18 at 22:51
  • 1
    @JBNizet I know that static type-checking is of no help during runtime. But declaring types helps detect wrong datatypes when the function is called somewhere else in the code. And since tsc acts not only as a type checker but also a transpiler, I was hoping for some workaround to get this thing possible – Deepak Aug 12 '18 at 22:52
  • 3
    @PatrickRoberts I have a very simple use-case. Just a single function having string as input. But I would like to have bounds on its length as well and make my editor warn me if I violate that rule during the call. I don't think there is any direct solution but some way to do it would be nice – Deepak Aug 12 '18 at 22:57
  • That is only feasible for statically declared strings. For strings from user-input or any dynamic source, it is impossible for the reason that JBNizet explained, so it's not very useful to statically check length. – Patrick Roberts Aug 13 '18 at 00:00
  • There have been [suggestions](https://github.com/Microsoft/TypeScript/issues/6579) to support some string validation in the type system, but nothing has made it into the language yet. – jcalz Aug 13 '18 at 00:03
  • Using branded types you can force consumers to do tests to validate that the strings are of valid length. I answered something similar here : https://stackoverflow.com/questions/49673001/define-a-type-in-typescript-with-conditional-properties-limits/49673307#49673307 – Titian Cernicova-Dragomir Aug 13 '18 at 05:26

1 Answers1

46

You can achieve this using a type constructor and something called a "Phantom Type" (read a nice article about this here) which is a technique to ensure that a type can not be assigned to a value directly.

Here's an example of a StringOfLength<Min,Max> type using these techniques:

type StringOfLength<Min, Max> = string & {
  min: Min;
  max: Max;
  StringOfLength: unique symbol // this is the phantom type
};

// This is a type guard function which can be used to assert that a string
// is of type StringOfLength<Min,Max>
const isStringOfLength = <Min extends number, Max extends number>(
  str: string,
  min: Min,
  max: Max
): str is StringOfLength<Min, Max> => str.length >= min && str.length <= max;
    
// type constructor function
export const stringOfLength = <Min extends number, Max extends number>(
  input: unknown,
  min: Min,
  max: Max
): StringOfLength<Min, Max> => {
  if (typeof input !== "string") {
    throw new Error("invalid input");
  }
    
  if (!isStringOfLength(input, min, max)) {
    throw new Error("input is not between specified min and max");
  }
    
  return input; // the type of input here is now StringOfLength<Min,Max>
};

// Now we can use our type constructor function
const myString = stringOfLength('hello', 1, 10) // myString has type StringOfLength<1,10>

// the type constructor fails if the input is invalid
stringOfLength('a', 5, 10) // Error: input is not between specified min and max

// The phantom type prevents us from assigning StringOfLength manually like this:
const a: StringOfLength<0, 10> = 'hello' // Type '"hello"' is not assignable to type { StringOfLength: unique symbol }

There are some limitations here - which are that you can't prevent someone from creating an invalid type like StringOfLength<-1, -300> but you can add runtime checks that the min and max values passed to the stringOfLength constructor function are valid.

Edit: This technique is now more commonly known as "branded types" in Typescript.

cdimitroulas
  • 2,380
  • 1
  • 15
  • 22
  • It throw me an error: `A property of an interface or type literal whose type is a 'unique symbol' type must be 'readonly'` – TOPKAT Nov 05 '21 at 10:21
  • 1
    Maybe that's an error from a newer Typescript version. If you add the `readonly` modifier it should remove the error: `readonly StringOfLength: unique symbol` – cdimitroulas Nov 05 '21 at 12:40
  • its worth mentioning that this does **not** check string length at complile time, even thought its using typescript, it will only let you know the string is outside the bounds at run time – benmneb Feb 01 '23 at 08:29
  • Depends what you mean exactly. At compile time it will prevent you from using an "unchecked" string (of type `string`) rather than a `StringOfLength`. It will also prevent you from using strings of the wrong length e.g. a `StringOfLength<1, 100>` is not assignable to a `StringOfLength<1, 50>` – cdimitroulas Feb 01 '23 at 12:08
  • 1
    For some reason, it doesn't seem to validate that at compile time. I am able to assign `StringOfLength` to `StringOfLength` where `x != a` and `y != b` and I don't see any compile errors. https://tsplay.dev/NnEExw – vighnesh153 Apr 07 '23 at 14:51
  • Thanks @vighnesh153, I've updated the code to fix this. the `Min` and `Max` type parameters needed to be included in the final type as well for this to work properly. https://tsplay.dev/mZZJKm – cdimitroulas Apr 11 '23 at 09:33