Solution
Playground
This innovative solution leverages the power of template literal types in TypeScript 4.1 to convert snake_case to camelCase:
type SnakeToCamelCase<S extends string> =
S extends `${infer T}_${infer U}` ?
`${T}${Capitalize<SnakeToCamelCase<U>>}` :
S
type T11 = SnakeToCamelCase<"hello"> // "hello"
type T12 = SnakeToCamelCase<"hello_world"> // "helloWorld"
type T13 = SnakeToCamelCase<"hello_ts_world"> // "helloTsWorld"
type T14 = SnakeToCamelCase<"hello_world" | "foo_bar">// "helloWorld" | "fooBar"
type T15 = SnakeToCamelCase<string> // string
type T16 = SnakeToCamelCase<`the_answer_is_${N}`>//"theAnswerIs42" (type N = 42)
This opens up possibilities for using key remapping in mapped types to create a new record type:
type OutputType = {[K in keyof InputType as SnakeToCamelCase<K>]: InputType[K]}
/*
type OutputType = {
snakeCaseKey1: number;
snakeCaseKey2: string;
}
*/
Extensions
Inversion type
type CamelToSnakeCase<S extends string> =
S extends `${infer T}${infer U}` ?
`${T extends Capitalize<T> ? "_" : ""}${Lowercase<T>}${CamelToSnakeCase<U>}` :
S
type T21 = CamelToSnakeCase<"hello"> // "hello"
type T22 = CamelToSnakeCase<"helloWorld"> // "hello_world"
type T23 = CamelToSnakeCase<"helloTsWorld"> // "hello_ts_world"
Pascal case, Kebab case and inversions
Building on these types, conversion between various cases like Pascal case and kebab case is simplified with intrinsic string types Capitalize
and Uncapitalize
:
type CamelToPascalCase<S extends string> = Capitalize<S>
type PascalToCamelCase<S extends string> = Uncapitalize<S>
type PascalToSnakeCase<S extends string> = CamelToSnakeCase<Uncapitalize<S>>
type SnakeToPascalCase<S extends string> = Capitalize<SnakeToCamelCase<S>>
To convert to kebab case, simply replace _
with -
in the snake case type.
Handling nested properties
type SnakeToCamelCaseNested<T> = T extends object ? {
[K in keyof T as SnakeToCamelCase<K & string>]: SnakeToCamelCaseNested<T[K]>
} : T
"Type instantiation is excessively deep and possibly infinite."
For lengthy strings that may cause this error, processing multiple sub-terms at once can manage type recursion effectively. Introducing SnakeToCamelCaseXXL
for such scenarios:
Playground
type SnakeToCamelCaseXXL<S extends string> =
S extends `${infer T}_${infer U}_${infer V}` ?
`${T}${Capitalize<U>}${Capitalize<SnakeToCamelCaseXXL<V>>}` :
S extends `${infer T}_${infer U}` ?
`${T}${Capitalize<SnakeToCamelCaseXXL<U>>}` :
S
Note: In the first condition, T
and U
each infer one sub-term, while V
infers the rest of the string.
Update: With TS 4.5 increasing the type instantiation depth limit, advanced features like tail recursive evaluation are now available for complex cases.