In this guide, you’ll learn how to transform object keys written in snake_case into camelCase using TypeScript. This recipe provides a practical and reusable solution that maintains compatibility with existing backend responses and ensures proper type safety.
Problem: Why We Need Key Transformation
In many projects, backend responses often use snake_case for key naming. For example, Go-based backends commonly follow this pattern. However, frontend development in JavaScript/TypeScript typically prefers camelCase.
This discrepancy can lead to mismatches and additional transformation layers in code, especially when auto-generated types (e.g., Swagger) are involved. Instead of manually handling these discrepancies, we can automate the process by transforming keys directly in TypeScript types.
Step 1: Convert Strings to camelCase
The first step is to create a recursive utility that converts a single string from snake_case to camelCase. Here’s the implementation:
type CamelCase<S> = S extends `${infer First}_${infer SecondFirst}${infer Rest}`
? `${First}${Uppercase<SecondFirst>}${CamelCase<Rest>}`
: S;Explanation:
1. Base Case: If the string contains no underscores (_), it remains unchanged. 2. Recursive Case: Split the string into three parts:
-
First: The portion before the first underscore. -
SecondFirst: The first character after the underscore, converted to uppercase. -
Rest: The remaining part of the string. 3. CombineFirst, the uppercase version ofSecondFirst, and recursively processRest.
Example:
For array_of_strings, the transformation works as follows:
- Split into
array,o, andf_strings. - Convert to
arrayOf_strings. - Continue recursively until no underscores remain, resulting in
arrayOfStrings.
Step 2: Transform Object Keys
Next, create a utility type to recursively transform all keys in an object type to camelCase.
type KeysToCamelCase<T> =
T extends Record<string, unknown>
? {
[K in keyof T as CamelCase<K>]: KeysToCamelCase<T[K]>;
}
: T extends Array<infer U>
? Array<KeysToCamelCase<U>>
: T;Explanation:
- Objects: Transform keys using CamelCase and recursively process their values.
- Arrays: Recursively process the array’s item type.
- Base Types: Return the original type for non-objects and non-arrays.
Complete Example
Here’s the complete code with a practical use case:
// Input Types
type CityOne = {
city_one: string;
};
type GetUserResponse = {
id: string;
user_name: string;
user_email?: string;
organization: {
array_of_strings: ['string_one', CityOne];
org_units_optional?: string[];
addresses: CityOne[];
several_different?: [{ type_one: string }, { type_two: string }];
};
};
// Transformation Utilities
type CamelCase<S> = S extends `${infer First}_${infer SecondFirst}${infer Rest}`
? `${First}${Uppercase<SecondFirst>}${CamelCase<Rest>}`
: S;
type KeysToCamelCase<T> =
T extends Record<string, unknown>
? {
[K in keyof T as CamelCase<K>]: KeysToCamelCase<T[K]>;
}
: T extends Array<infer U>
? Array<KeysToCamelCase<U>>
: T;
// Transformed Type
type CameledRecord = KeysToCamelCase<GetUserResponse>;Result:
CameledRecord has all keys transformed into camelCase, including nested types and arrays, while preserving optional fields and other type details.
Conclusion
By combining the CamelCase and KeysToCamelCase utilities, you can effortlessly transform any TypeScript type from snake_case to camelCase. This approach is particularly useful for integrating with backends that follow different naming conventions. With this recipe, you can ensure consistency in your frontend code while maintaining strong type safety.