Home > Software engineering >  Typescript - Create our own StringType utility types
Typescript - Create our own StringType utility types

Time:08-14

Is there a way to define our own StringType utility types such as Kebabcase<StringType>, Camelcase<StringType>, Snakecase<StringType>, Pascalcase<StringType>, ...

Like Uppercase<StringType> or Capitalize<StringType> ?

I have checked this page in the Typescript doc to see how these native utility types are defined. However it doesn't seem possible to reuse the same logic as there come built-in to the compiler.

I still ask the question in case something similar exists.

CodePudding user response:

Foreword

I've found a library called type-fest which has utility types for all your cases (and more). You can inspects its source to see how they do it. It's written by a person who has put much more thought in all the possible cases to handle. I'll leave my original answer because the theory still stands, the demo is an interesting naive implementation and the listed further reading is insightful.


Theory

It it possible to combine type inference and string manipulation recursively to achieve this. The general pattern* is:

type CaseManipulation<T extends string> =
//                    ^^^^^^^^^^^^^^^^
//                  Constrain to strings
  T extends `${infer Head} ${infer Tail}` ?
//           ^^^^^^^^^^^^^
// Infer the characters
// before the first space
//                          ^^^^^^^^^^^^                           
//               Infer all the following characters
    `${Lowercase<Head>}-${CaseManipulation<Tail>}` : Lowercase<T>;
// Apply any manipulations to returned result and recursively apply to the tail. The example here is with kebab case.

Demo

Here's an demo with a with all the cases you've listed*:

type foo = "foo";
type fooBar = "foo bar";
type fooBarBaz = "foo bar baz";
type fooBarBazQux = "foo bar baz qux";

type CamelCase<T extends string> = T extends `${infer Head} ${infer Tail}` ? `${Uncapitalize<Head>}${CamelCase<Capitalize<Tail>>}` : Lowercase<T>;

type cameled0 = CamelCase<foo>;
type cameled1 = CamelCase<fooBar>;
type cameled2 = CamelCase<fooBarBaz>;
type cameled3 = CamelCase<fooBarBazQux>;

type PascalCase<T extends string> = T extends `${infer Head} ${infer Tail}` ? `${Capitalize<Head>}${PascalCase<Tail>}` : Capitalize<T>;

type pascaled0 = PascalCase<foo>;
type pascaled1 = PascalCase<fooBar>;
type pascaled2 = PascalCase<fooBarBaz>;
type pascaled3 = PascalCase<fooBarBazQux>;


type KebabCase<T extends string> = T extends `${infer Head} ${infer Tail}` ? `${Lowercase<Head>}-${KebabCase<Tail>}` : Lowercase<T>;

type kebabed0 = KebabCase<foo>;
type kebabed1 = KebabCase<fooBar>;
type kebabed2 = KebabCase<fooBarBaz>;
type kebabed3 = KebabCase<fooBarBazQux>;

type SnakeCase<T extends string> = T extends `${infer Head} ${infer Tail}` ? `${Lowercase<Head>}_${SnakeCase<Tail>}` : Lowercase<T>;

type snaked0 = SnakeCase<foo>;
type snaked1 = SnakeCase<fooBar>;
type snaked2 = SnakeCase<fooBarBaz>;
type snaked3 = SnakeCase<fooBarBazQux>;

* caveat I haven't tried to be comprehensive with the possible inputs/outputs but it should give a general idea of a solution

Further reading

  • Related