我正在开发一个解析CSV并返回类型化对象数组的函数.它看起来像这样.

type KeyToTransformerLookup<T> = {
  [K in keyof T as T[K] extends string ? never : K]: (
    stringValue: string
  ) => T[K];
};

export default function csvToArray<T>(
  csv: string,
  keys: Array<keyof T>,
  keyToTransformerLookup: KeyToTransformerLookup<T>,
  ignoreFirstLine = false
): T[] {
  const rows = csv.split(/\n(?=.)/);

  if (ignoreFirstLine) {
    rows.shift();
  }

  return rows.map((row) => {
    const values = row.split(/(?!\B"[^"]*),(?![^"]*"\B)/g);

    const rowObject: Record<any, any> = {};

    keys.forEach((key, index) => {
      const stringValue = values[index];

      if (key in keyToTransformerLookup) {
        rowObject[key] = keyToTransformerLookup[key](stringValue); // TypeScript complains
        return;
      }

      rowObject[key] = stringValue;
    });

    return rowObject as T;
  });
}
Type 'keyof T' cannot be used to index type 'KeyToTransformerLookup<T>'.

我猜这是因为一旦映射了K in keyof T,结果类型就不再是keyof T的部分,而是一个全新的类型.这有点令人沮丧,因为我没有转换键,而且我还在判断keyToTransformerLookup上是否存在key,所以我只想找到一种满足TypeScrip的方式来索引keyToTransformerLookup.

推荐答案

TypeScrip对narrowing via the in operator的支持(也请参见TS4.9 update to this)目前只影响被判断的object的外观类型,而不影响key.也就是说,if (k in o) {⋯} else {⋯}只意味着o,而不是k.在microsoft/TypeScript#43284有一个开放的功能请求,要求也添加对缩小k的支持,但它还没有实现(截至TS5.0)和it might be tricky to implement to everyone's satisfaction.

因此,当你勾选key in keyToTransformerLookup时,key的类型顽固地保持在keyof T,不能缩小到keyof KeyToTransformerLookup<T>.


除非TypeScrip增加了对您想要进行的缩小范围的支持,否则您将不得不绕过它.一次性判断的最简单解决方法是仅将类型缩小到assert:

if (key in keyToTransformerLookup) {
  rowObject[key] = keyToTransformerLookup[
    key as keyof KeyToTransformerLookup<T>
  ](stringValue);
  return;
}

如果你发现自己经常进行这种判断,你可以写custom type guard function分:

function isKeyof<T extends object>(
  k: PropertyKey, o: T): k is keyof T {
  return k in o;
}

然后使用它:

if (isKeyof(key, keyToTransformerLookup)) {
  rowObject[key] = keyToTransformerLookup[key](stringValue);
  return;
}

请注意,这样缩小范围是不安全的.TypeScrip中的对象类型是openextendible,并且不是密封的或"精确的"(如microsoft/TypeScript#12936中所讨论的).对象可以具有比其类型中描述的属性更多的属性.这意味着这样的事情可能会发生:

interface Foo {
  x: string;
  y: number;
}
const xformer = {
  y: (s: string) => +s,
  x: "oopsiedoodle" // <-- this is an excess property
}
csvToArray<Foo>("...", ["x", "y"], xformer); // no compiler error, but:
// ? RUNTIME ERROR! keyToTransformerLookup[key] is not a function

这可能不太可能,但你应该知道这一点.有关详细信息,请参阅extend and only specify known properties?.

Playground link to code

Typescript相关问答推荐

Angular -使用Phone Directive将值粘贴到控件以格式化值时验证器不工作

类型脚本不会推断键的值类型

使用FormArray在Angular中添加排序

即使子网是公共的,AWS CDK EventBridge ECS任务也无法分配公共IP地址

打印脚本丢失函数的泛型上下文

在动态对话框中使用STEP组件实现布线

material 表不渲染任何数据

状态更新后未触发特定元素的Reaction CSS转换

如何根据特定操作隐藏按钮

打印脚本中的动态函数重载

如何解决类型T不能用于索引类型{ A:typeof A; B:typeof B;. }'

React Typescript项目问题有Redux-Toolkit userSlice角色问题

Angular 自定义验证控件未获得表单值

刷新时保持当前名称的Angular

在Thunk中间件中输入额外参数时Redux工具包的打字脚本错误

扩展对象的此内容(&Q;)

我可以使用JsDocs来澄清Typescript实用程序类型吗?

确保财产存在

使用本机 Promise 覆盖 WinJS Promise 作为 Chrome 扩展内容脚本?

剧作家测试未加载本地网址