我正在纠结于一个问题.我不知道有没有可能用Typescript ...

这里的目标是获取所有对象键并进行一些转换,但不会丢失类型安全功能.例如,如果我有一个带有关键字的对象,我想将所有的关键字转换为蛇形大小写.这就是我能接近的程度:

// 5.0.2 TypeScript Version

type InputType = {
  'first-name': string;
};

function transform<T>(input: any) {
  const result = {};
  for (const key of input) {
    const parsedKey = parseKey(key);
    result[parsedKey] = input[key];
  }
  return result as Required<T>;
}

function parseKey(input: string) {
  return input.replace(/(\-)/g, '_');
}

const before = { 'first-name': 'Fernando' };
const after = transform<InputType>(before);

// INFO: This works but it isn't what I want...
after['first-name'];

// INFO: This is what I want, but it isn't work...
after['first_name'];

// INFO: The "output type" should be something like this:
//
// type OutputType = {
//   'first_name': string;
// };

提前谢谢你

推荐答案

您可以编写KebabToSnake<T>实用程序类型,将字符串literal typeskebab case转换为snake case,方法是将破折号替换为下划线:

type KebabToSnake<T extends string, A extends string = ""> =
  T extends `${infer L}-${infer R}` ?
  KebabToSnake<R, `${A}${L}_`> : `${A}${T}`

这是一个recursive conditional type,它使用template literal types来解析和连接字符串文字.它是一个tail-recursive conditional type,所以它应该表现良好,即使是非常长的弦.

它的工作方式:它接受输入字符串文字类型T,并构建或accumulates输出字符串文字类型A.如果T包含破折号,则它在第一个破折号处将字符串拆分成左侧L和右侧R.然后,我们通过使用右侧R作为要解析的新字符串,并通过将L和下划线附加到A作为新的累积输出来递归.如果T不包含破折号,则只需将T附加到累计输出并返回即可.

让我们来测试一下:

type Test1 = 
   KebabToSnake<"a-bc-def-ghij-klm-no-p-q-r-s-t--u---v----x-----y--------z">;
// type Test1 = "a_bc_def_ghij_klm_no_p_q_r_s_t__u___v____x_____y________z";

好的,看起来不错.


然后,我们可以给transform()一个generic调用签名,该签名接受类型Tinput对象,并返回mapped type,其中来自T的每个与string兼容的属性密钥KremappedKebabToSnake<K>,并且每个属性值保持不变:

declare function transform<T extends object>(
  input: T
): { [K in Extract<keyof T, string> as KebabToSnake<K>]: T[K] }

让我们确保这对呼叫者有效:

const before = {
  'first-name': 'Fernando',
  'number-of-fingers-and-toes': 20,
  'is-a-human-being-or-other-mammal': true
};
const after = transform(before);
/* const after: {
    first_name: string;
    number_of_fingers_and_toes: number;
    is_a_human_being_or_other_mammal: boolean;
} */

看起来不错,输出类型与输入类型相同,只是键已从烤肉串转换为蛇形.


至于实现,这基本上是相同的(除了我在迭代键时使用in而不是of;您的是拼写错误吗?)编译器基本上无法验证是否可以将任何特定值赋给复杂的泛型条件类型(如KebabToSnake<T>)或其重新映射版本.我们不会试图让编译器做一些它不能做的事情,而是通过使用the intentionally unsafe any type告诉编译器不要担心类型:

function transform<T extends object>(
  input: T
): { [K in Extract<keyof T, string> as KebabToSnake<K>]: T[K] } {
  const result: any = {};
  for (const key in input) {
    const parsedKey = parseKey(key);
    result[parsedKey] = input[key];
  }
  return result;
}

编译时没有任何错误.最后,让我们确保它确实像广告中所说的那样有效:

console.log(after.first_name.toUpperCase()); // "FERNANDO"
console.log(after.number_of_fingers_and_toes.toFixed(1)) // "20.0"

看上go 不错.编译器知道after的类型,并且运行时值与其一致.

Playground link to code

Typescript相关问答推荐

mat-table中的动态matHeaderRowDef Angular

当类型不能undefined时,如何使编译器抛出错误?

具有条件通用props 的react 类型脚本组件错误地推断类型

在角形加载组件之前无法获取最新令牌

如何根据数据类型动态注入组件?

typescript抱怨值可以是未定义的,尽管类型没有定义

类型脚本接口索引签名

路由链接不会导航到指定router-outlet 的延迟加载模块

带switch 的函数的返回类型的类型缩小

如何将CSV/TXT文件导入到服务中?

有没有可能为这样的函数写一个类型?

TypeScrip泛型类未提供预期的类型错误

Typescript 类型守卫一个类泛型?

为什么TypeScrip不能解析对象析构中的赋值?

使用或属性编写无限嵌套的接口

抽象类对派生类强制不同的构造函数签名

如何键入派生类的实例方法,以便它调用另一个实例方法?

如何使函数参数数组不相交?

typescript如何从映射类型推断

为什么我会得到一个;并不是所有的代码路径都返回一个值0;switch 中的所有情况何时返回或抛出?(来自vsCode/TypeScript)