我正在try 编写一个打字脚本函数,将驼峰大小写转换为蛇形大小写.该函数应该能够处理对象和对象array.我希望将输入和输出类型指定为泛型.如果输入不是对象或对象数组,我只想原封不动地返回输入值.

我面临的问题是,由于返回类型是一个联合类型,TypeScript在使用函数时不知道返回的是哪个类型.

以下是我目前的实现:

function deepConvertToSnakeCase<I, O>(item: I): I | O {
    if (Array.isArray(item)) {
        return item.map((el) => deepConvertToSnakeCase(el)) as O;
    }

    if (item !== Object(item)) {
        return item as I;
    }

    return Object.fromEntries(
        Object.entries(item as Array<[string, unknown]>).map(([key, value]) => [
            key.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(),
            deepConvertToSnakeCase(value)
        ])
    ) as O;
}

// usage

interface CamelCaseObject {
    keyItem: 'value';
    keyItemAlso: 'value';
}

interface SnakeCaseObject {
    key_item: 'value';
    key_item_also: 'value';
}

const data: CamelCaseObject = {
    keyItem: 'value',
    keyItemAlso: 'value'
};

const convertedData = deepConvertToSnakeCase<CamelCaseObject, SnakeCaseObject>(data);

// Result: convertedData type is CamelCaseObject | SnakeCaseObject
// Expected result: convertedData type is SnakeCaseObject

编辑:最小可重现示例 编辑:问题编辑

推荐答案

在调用deepConvertToSnakeCase()时,您似乎希望手动并显式地指定IOgeneric个类型参数,但您希望函数的返回类型为IO(或者可能是O的数组),具体取决于I的类型.这意味着您需要将判断I的返回类型设置为conditional type.就像这样:

function deepConvertToSnakeCase<I, O>(item: I):
  I extends readonly any[] ? O[] : I extends object ? O : I {
   /* snip ✂ ⋯ ✂ snip */
}

因此,如果I是类似数组的,则您的输出是O的array.否则,如果I是类对象,则输出为O.否则,如果I是基元,那么您的输出就是I.这为呼叫者提供了您正在寻找的行为:

const convertedData =
  deepConvertToSnakeCase<CamelCaseObject, SnakeCaseObject>(data);
// const convertedData: SnakeCaseObject

const convertedData2 =
  deepConvertToSnakeCase<string, SnakeCaseObject>("abc");
// const convertedData2: string

const convertedData23 =
  deepConvertToSnakeCase<CamelCaseObject[], SnakeCaseObject>([data]);
// const convertedData23: SnakeCaseObject[]

当然,deepConvertToSnakeCase()的实现不可能验证输出是否真的是该条件类型.您需要执行类似于assert的操作,您的返回值是所需的类型.您已经在这样做了(使用as Ias O),并且您必须使用新的返回类型继续这样做.最简单的方法是使用the any type:

function deepConvertToSnakeCase<I, O>(item: I):
  I extends readonly any[] ? O[] : I extends object ? O : I {
  if (Array.isArray(item)) {
    return item.map((el) => deepConvertToSnakeCase(el)) as any;
  }
  if (item !== Object(item)) {
    return item as any;
  }
  return Object.fromEntries(
    Object.entries(item as Array<[string, unknown]>).map(([key, value]) => [
      key.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(),
      deepConvertToSnakeCase(value)
    ])
  ) as any;
}

这解决了被问到的问题.但请注意,这很容易让您欺骗编译器.虽然可以对照I判断输入类型,但编译器不知道deepConvertToSnakeCase()的实现是否真的会产生类型O的值.您可以为O指定任何您想要的类型,编译器将接受它.这意味着没有什么能阻止某个疯子打这个电话:

const nothingStopsThis =
  deepConvertToSnakeCase<{ a: string }, { b: number }>({ a: "" });
// const nothingStopsThis: {b: number};

这里有人声称{a: ""}将被转换为{b: number},而编译器相信了这一说法.但它当然不会,所以在运行时可能会发生不好的事情.

确实有可能编写一个从template literal typescompute的类型实用程序,而O对于给定的I应该是这样的,这样,比如说,{abCd: string}在类型系统中将被转换为{ab_cd: string}.这对调用者来说会安全得多(尽管实现仍然需要类型断言).这超出了所问问题的范围,因此我不会在实用程序类型的实现上进一步离题.关键是:调用此函数时要小心,不要使用错误的O.

Playground link to code

Typescript相关问答推荐

在这种情况下,如何获得类型安全函数的返回值?

如何使类型只能有来自另一个类型的键,但键可以有一个新值,新键应该抛出一个错误

TypeScript类型不匹配:在返回类型中处理已经声明的Promise Wrapper

如何访问Content UI的DatePicker/中的sx props of year picker?'<>

具有动态键的泛型类型

TypeScrip:基于参数的条件返回类型

如何提取密钥及其对应的属性类型,以供在新类型中使用?

使用axios在ngOnInit中初始化列表

返回具有递归属性的泛型类型的泛型函数

如何使用类型谓词将类型限制为不可赋值的类型?

<;T扩展布尔值>;

TypeScrip-根据一个参数值验证另一个参数值

在Google授权后由客户端接收令牌

字符串文字联合的分布式条件类型

有没有办法在Reaction组件中动态导入打字脚本`type`?

如何在Type脚本中创建一个只接受两个对象之间具有相同值类型的键的接口?

如何使用 Angular 确定给定值是否与应用程序中特定单选按钮的值匹配?

Typescript 从泛型推断类型

基于可选参数的动态类型泛型类型

如何使用 TypeScript 接口中第一个属性的值定义第二个属性