Typescript 使用泛型重用另一个参数中第一个参数的键

Typescript 使用泛型重用另一个参数中第一个参数的键,typescript,Typescript,我一直在尝试用谷歌搜索如何解决这个问题,但我甚至很难找到要搜索的内容,所以任何编辑这个问题或将我链接到副本的帮助都会非常有用 用例 我有一个函数,作为第一个参数,它以“列名”记录作为键,以接口字段作为值。例如 { “用户id”:“用户id”, “电子邮件”:“电子邮件”, } 以及一个 接口预期形状{ userID:number; 电子邮件:字符串; } 铸造 出于我无法控制的原因,上面显示我的数据的列被命名为user\u id和email\u,并将始终作为字符串返回。因此,我需要定义在创建

我一直在尝试用谷歌搜索如何解决这个问题,但我甚至很难找到要搜索的内容,所以任何编辑这个问题或将我链接到副本的帮助都会非常有用

用例 我有一个函数,作为第一个参数,它以“列名”记录作为键,以接口字段作为值。例如

{
“用户id”:“用户id”,
“电子邮件”:“电子邮件”,
}
以及一个

接口预期形状{
userID:number;
电子邮件:字符串;
}
铸造 出于我无法控制的原因,上面显示我的数据的列被命名为
user\u id
email\u
,并将始终作为字符串返回。因此,我需要定义在创建与
ExpectedShape
接口匹配的JS对象时如何强制转换或清理传入数据

因此,为了实现这一点,我在方法中有一个配置变量,它将采用与原始映射相同的键,但有相应的方法来转换内容。比如说,

{
“用户id”:Number.parseInt,
“电子邮件”:sanitizeEmail,
}
因此,当
user\u id
从数据源进入时,它将调用
Number.parseInt
,以便在构建
ExpectedShape
对象时,它将是一个正确的数字

我想做什么 通过TypeScript,我希望强制执行“映射”和“转换操作”的键相同

下面是一些坏掉的代码,它不起作用,但显示了我的设想

接口预期形状{
userID:number;
电子邮件:字符串;
}
接口功能选项{
铸造:记录S[keyof S]>;
}
函数myFunction(
值:记录,
配置:FunctionOptions,//(
值:M,
配置:函数选项,
):部分{
返回{};
}

但是,它不能正确地执行键,并且允许我使用我想要的任何键。

我认为,为了在运行时或在类型系统中工作,需要传入类似三个参数的内容:要处理的输入对象;将输入对象的键映射到输出对象的键的对象;以及对象,该对象将输入对象的值映射到输出对象的值。可能键映射器只是一个值仅为键的对象,而值映射器是一个值为函数的对象

下面是一个可能的实现:

function mapKeysAndValues<I extends object,
  KM extends Record<keyof I, S>,
  VM extends { [K in keyof I]: (v: I[K]) => any },
  S extends PropertyKey
>(
  inputObject: I,
  keyMapping: KM,
  valMapping: VM
): { [K in keyof I as KM[K]]: ReturnType<VM[K]> } {

  const ret: any = {};
  (Object.keys(inputObject) as Array<keyof I>).forEach(k =>
    ret[keyMapping[k]] = valMapping[k](inputObject[k] as any)
  )
  return ret;
}
看起来不错!编译器知道
mapKeysAndValues()
将生成
ExpectedShape
类型的值,当我们将其记录到控制台时,我们看到在运行时也发生了正确的事情

希望您可以使用
mapKeysAndValues()
作为您试图完成的转换的起点


我认为,为了在运行时或在类型系统中实现这一点,您需要传入三个参数:要处理的输入对象;将输入对象的键映射到输出对象的键的对象;以及将输入对象的值映射到输出对象的值的对象可能键映射器只是一个值只是键的对象,而值映射器是一个值是函数的对象

下面是一个可能的实现:

function mapKeysAndValues<I extends object,
  KM extends Record<keyof I, S>,
  VM extends { [K in keyof I]: (v: I[K]) => any },
  S extends PropertyKey
>(
  inputObject: I,
  keyMapping: KM,
  valMapping: VM
): { [K in keyof I as KM[K]]: ReturnType<VM[K]> } {

  const ret: any = {};
  (Object.keys(inputObject) as Array<keyof I>).forEach(k =>
    ret[keyMapping[k]] = valMapping[k](inputObject[k] as any)
  )
  return ret;
}
看起来不错!编译器知道
mapKeysAndValues()
将生成
ExpectedShape
类型的值,当我们将其记录到控制台时,我们看到在运行时也发生了正确的事情

希望您可以使用
mapKeysAndValues()
作为您试图完成的转换的起点


您的“坏”代码似乎有打字错误或其他问题,使我无法完全理解您所做工作的确切性质(难道
values
应该是键到键的映射吗?如果是这样,为什么要传入
Number.parseInt
sanitizeEmail
?当您甚至没有传入原始API中的值时,
myFunction
怎么能返回
部分
)。我的答案将只涉及在运行时和类型系统中执行此操作的一般方法,希望您可以根据您的特定用例进行调整。您的“损坏”代码似乎有拼写错误或其他阻碍我完全理解您所做操作的确切性质的内容(难道
values
应该是键到键的映射吗?如果是这样,为什么要传入
Number.parseInt
sanitizeEmail
?当您甚至没有传入原始API中的值时,
myFunction
怎么能返回
部分
)。我的答案将只涉及在运行时和类型系统中执行此操作的一般方法,希望您能够根据您的特定用例进行调整。看起来我在问题的示例代码中犯了一些错误,我已经修复了这些错误。但是您对我的意思和回答问题的假设是正确的!谢谢!此魔法能够为了让我得到我想要的东西,看起来我在我的问题的示例代码中犯了一些错误,我已经修正了。但是你对我的意思和回答我的问题的假设是正确的!谢谢!这个魔法能够让我得到我想要的