1

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.

2 Answers 2

3

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 a 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

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

1 Comment

Typescript is truly crazy
0

Yes, you are able to create your own custom string utility types in TypeScript using template literal types and conditional types, just like the built-in TypeScript utility types.

This is an example of how you could implement your own custom utility types:

I used KebabCase in this example

type KebabCase<S extends string> =
  S extends `${infer Head}${infer Tail}`
    ? Tail extends Uncapitalize<Tail>
      ? `${Lowercase<Head>}${KebabCase<Tail>}`
      : `${Lowercase<Head>}-${KebabCase<Tail>}`
    : S;
type Test = KebabCase<"helloWorld">;

This code should work for camel-case to kebab-case inputs. However, it assumes every uppercase letter signals a new word, and thus won't handle edge cases such as acronyms.

I have used 'infer' to split strings as well as Uncapitalize<> and Lowercase<> to implement the custom utility type.

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.