如何实现TypeScript接口/类型在蛇形命名(snake_case)与驼峰命名(camelCase)间的双向转换?
如何实现TypeScript接口/类型在蛇形命名(snake_case)与驼峰命名(camelCase)间的双向转换?
刚好之前做项目的时候也遇到过一模一样的需求,给你整理两个核心部分:类型层面的类型别名推导(让TypeScript能正确提示驼峰/蛇形的属性)和运行时的对象转换函数(实际处理API数据的结构转换),直接就能用:
一、先搞定类型层面的映射(编译时)
首先我们需要写两个类型工具,分别处理蛇形到驼峰、驼峰到蛇形的键名转换,再递归处理嵌套的对象和数组:
// 蛇形字符串转驼峰字符串的类型工具 type SnakeToCamel<S extends string> = S extends `${infer T}_${infer U}` ? `${T}${Capitalize<SnakeToCamel<U>>}` : S; // 驼峰字符串转蛇形字符串的类型工具 type CamelToSnake<S extends string> = S extends `${infer T}${infer U}` ? T extends Lowercase<T> ? `${T}${CamelToSnake<U>}` : `_${Lowercase<T>}${CamelToSnake<U>}` : S; // 递归处理对象所有属性的类型转换,支持嵌套对象和数组 type ConvertKeys<T, Converter extends (s: string) => string> = T extends Record<string, any> ? { [K in keyof T as Converter extends (s: string) => infer R ? R : never]: T[K] extends Array<infer U> ? Array<ConvertKeys<U, Converter>> : ConvertKeys<T[K], Converter> } : T;
有了这几个类型工具,你就可以基于API的蛇形接口,一键生成前端用的驼峰接口,反过来也一样:
// API返回的蛇形命名接口 interface MergedReviewDetailsSnake { parent_review: number; child_reviews: number[]; } // 自动生成前端用的驼峰命名接口 type MergedReviewDetailsCamel = ConvertKeys<MergedReviewDetailsSnake, SnakeToCamel>; // 此时MergedReviewDetailsCamel的结构就是 { parentReview: number; childReviews: number[]; } // 如果需要从驼峰接口生成蛇形接口也可以 // type MergedReviewDetailsSnakeAgain = ConvertKeys<MergedReviewDetailsCamel, CamelToSnake>;
二、再写运行时的转换函数(实际处理数据)
类型只是编译时的提示,实际API返回的是普通JS对象,我们需要写运行时函数来真正转换键名:
// 单个字符串:蛇形转驼峰 function snakeToCamelStr(str: string): string { return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); } // 单个字符串:驼峰转蛇形 function camelToSnakeStr(str: string): string { return str.replace(/([A-Z])/g, '_$1').toLowerCase(); } // 递归转换整个对象/数组:蛇形转驼峰 function convertSnakeToCamel<T>(obj: T): ConvertKeys<T, SnakeToCamel> { // 处理数组 if (Array.isArray(obj)) { return obj.map(item => convertSnakeToCamel(item)) as unknown as ConvertKeys<T, SnakeToCamel>; } // 处理对象(排除Date等特殊对象) if (obj && typeof obj === 'object' && !(obj instanceof Date)) { return Object.fromEntries( Object.entries(obj).map(([key, value]) => [ snakeToCamelStr(key), convertSnakeToCamel(value) ]) ) as unknown as ConvertKeys<T, SnakeToCamel>; } // 基本类型直接返回 return obj as unknown as ConvertKeys<T, SnakeToCamel>; } // 递归转换整个对象/数组:驼峰转蛇形 function convertCamelToSnake<T>(obj: T): ConvertKeys<T, CamelToSnake> { if (Array.isArray(obj)) { return obj.map(item => convertCamelToSnake(item)) as unknown as ConvertKeys<T, CamelToSnake>; } if (obj && typeof obj === 'object' && !(obj instanceof Date)) { return Object.fromEntries( Object.entries(obj).map(([key, value]) => [ camelToSnakeStr(key), convertCamelToSnake(value) ]) ) as unknown as ConvertKeys<T, CamelToSnake>; } return obj as unknown as ConvertKeys<T, CamelToSnake>; }
三、实际使用示例
直接把API数据丢进去转换就行,TypeScript还会自动提示正确的属性名:
// 模拟API返回的蛇形数据 const apiResponse = { parent_review: 1001, child_reviews: [1002, 1003, 1004] }; // 转成驼峰格式给前端用 const frontendData = convertSnakeToCamel(apiResponse); // 此时frontendData.parentReview、frontendData.childReviews 都会有正确的类型提示 // 前端组装好的驼峰数据 const submitData = { parentReview: 1001, childReviews: [1002, 1003, 1005] }; // 转成蛇形格式提交给API const apiSubmitData = convertCamelToSnake(submitData); // 此时apiSubmitData的结构就是 { parent_review: 1001; child_reviews: [1002, 1003, 1005]; }
一些小提示
- 上面的函数已经处理了嵌套对象和数组的场景,比如
{ user_info: { user_name: "xxx" } }会转成{ userInfo: { userName: "xxx" } } - 如果遇到特殊命名(比如全大写的缩写如
user_id_card转userIDCard),可以调整正则或者加个特殊处理的逻辑,大部分常规场景直接用没问题 - 类型工具和转换函数是对应的,转换后的对象类型会自动匹配,不用手动声明
内容来源于stack exchange
相关产品推荐
相关产品推荐

