【问题标题】:TypeScript convert generic object from snake to camel caseTypeScript 将通用对象从蛇形转换为驼峰形
【发布时间】:2020-06-01 19:51:21
【问题描述】:

我想编写一个函数,它接受带有蛇形键的对象并将其转换为带有驼峰键的对象。假设我们知道输入对象的类型,但希望解决方案是通用的,那么在 TypeScript 中输入此类函数的最佳方式是什么。

type InputType = {
  snake_case_key_1: number,
  snake_case_key_2: string,
  ...
}

function snakeToCamelCase(object: T): U {
  ...
}

键入TU 的最佳工作是什么。

我希望U 的类型尽可能窄,U 的类型最好基于T

理想情况下,如果 T 是我的示例 InputType 我希望将 U 输入为

{
  snakeCaseKey1: number,
  snakeCaseKey2: string,
  ...
}

【问题讨论】:

  • 如果你知道输入类型,为什么还要使用泛型?
  • 对不起,我想我的意思是说我想接受一个已知的输入类型(不是带有string 键的通用对象) - 我想让输入通用
  • 我认为不可能生成强类型返回类型U。据我所知,您必须手动将键值转换为新类型。 p.s.我认为您的问题在编辑之前更有意义。
  • @Zze 谢谢,我再次编辑了它,希望它更清晰
  • 这确实是不可能的; Typescript 没有类型运算符可以作用于字符串文字类型以将它们转换为其他字符串文字类型,因此无法以这种方式转换任意属性名称。

标签: typescript


【解决方案1】:

解决方案

Playground

这可以通过 TypeScript 4.1 中的 template literal types 实现(另请参阅 snake_case):

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)

然后您将能够使用key remapping in mapped types 构造新的记录类型:

type OutputType = {[K in keyof InputType as SnakeToCamelCase<K>]: InputType[K]}
/* 
  type OutputType = {
      snakeCaseKey1: number;
      snakeCaseKey2: string;
  }
*/

扩展

反转类型

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 和 inversions

一旦你得到以上类型,使用intrinsic string typesCapitalizeUncapitalize就可以很简单地在它们和其他情况之间进行转换:

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

对于kebab case,将snake case类型的_替换为-

转换嵌套属性

type SnakeToCamelCaseNested<T> = T extends object ? {
  [K in keyof T as SnakeToCamelCase<K & string>]: SnakeToCamelCaseNested<T[K]>
} : T

“类型实例化太深,可能无限。”

这个错误可能发生在很长的字符串上。您可以一次性处理多个子项,以将类型递归限制在编译器可接受的范围内。例如。 SnakeToCamelCaseXXL:

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

注意:在第一个条件中,TU 各推断一个子项,而V 推断字符串的其余部分。

更新:TS 4.5 将类型实例化深度限制从 50 提高到 100,因此新版本不需要此编译器技巧。对于更复杂的情况,您现在还可以使用tail recursive evaluation

【讨论】:

  • 我喜欢阅读这篇文章,感谢您的回答。你能指导我如何阅读这个技巧吗?我看到它正在接受通用&lt;S extends string&gt;,然后它正在检查string extends S。它让我感到轻微的头痛。 :)
  • 当然 - 我最初有一个不同的版本,其中条件类型在 false 分支中返回 never 而不是 S,因此该子句将确保返回 string。不过,您有一个很好的观点,这个版本不需要string extends S。我会更新答案 - 希望已经消除了头痛;)
  • @ness-EE SnakeToCamelCase 希望您输入一个仅包含小写字符 (snake_case) 的术语,如 here 所述。要转换@​​987654351@,可以change the false branch to Lowercase&lt;S&gt;
  • @Calvin 更新了答案,看看“转换嵌套属性”
  • 这不是将 Array 原型中的属性驼峰化吗? (如push、pop等)
【解决方案2】:

FWIW,我遇到了some issues 的类型来自@ford04 的答案。我发现 CamelCasedPropertiesSnakeCasedPropertieshttps://github.com/sindresorhus/type-fest 到目前为止工作良好。

【讨论】:

  • 这些“类型实例化太深而且可能无限”。递归深度误差可以通过在上述解决方案中添加两行来解决。您可以尝试使用更新答案中的游乐场示例。
  • 我喜欢@ford04 在他的回答中分享的努力,但我会选择你分享的图书馆!
【解决方案3】:

不幸的是,这样的事情是不可能的。当前形式的 Typescript 不支持类型键转换/映射。

【讨论】:

  • 没有什么可以阻止您手动迭代对象的属性,操作它们的键,并以操作的形式将它们写入另一个对象。然而,这不是问题所在——它询问的是输入这样一个函数,而不是它的实现。
  • 不正确:keyof 运算符存在。
  • 我所说的转换/映射是类型的转换,而不是值的转换。
  • 这是少数几个我觉得 TypeScript 让你远离“更好”的通用解决方案的例子之一。将蛇写成驼峰式函数似乎是完全合理的,但是你不能这样做而不损失大量的类型安全。有点不幸。
猜你喜欢
  • 2023-03-11
  • 2016-08-07
  • 1970-01-01
  • 2018-10-17
  • 1970-01-01
  • 1970-01-01
  • 2020-01-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多