typescript - 高级 TS 类型:镜像键但成为新值的包装器类型
问题描述
我正在尝试编写一个需要一些接口的类型定义,如下所示:
interface IMyInterface {
name: string
subobject: {
boolField: boolean
}
}
然后OptionsTransformer
将其包装起来,对于每一key-value
对,保持密钥相同,但将值更改为新的 class FieldOptions<type>
。例如:
const transformed: OptionsTransformer<IMyInterface> = {
name: new FieldOptions<string>(),
subobject: {
boolField: new FieldOptions<boolean>()
}
}
这是因为我知道TS
接口在运行时会消失,因此您无法在运行时真正检查它们。我希望能够编写如下函数(我的实际用例是在 post 请求的正文中输入:我知道它应该是什么样子,并想验证它看起来像那样):
const runtimeVerifier<T> = (
options: OptionsTransformer<T>, input: Record<string, unknown>
) => {
//for each value in input, run the it's verifier. If it's a sub-object,
//recurse and do runtime verifier on the sub-object.
}
另一个要求是,在编译时,如果我向 中添加一个字段IMyInterface
,我希望TS
编译器在那里给我一个错误,例如“您的 optionsTransformer 缺少该字段。”。
我对这个定义已经很远了:
//this class doesn't matter, but here for completeness
class FieldOption<T> {}
export interface IValidBody {
[x: string]: boolean | string | number | IValidBody
}
export type OptionsTransformer<T extends IValidBody> = {
[key in keyof T]-?: T[key] extends IValidBody
? IOptionsForFields<T[key]>
: FieldOption<T[key]>
}
但是我在子对象上遇到了一些非常复杂的错误,并且无法破译它们。
任何帮助,将不胜感激。
解决方案
经过几天的黑客攻击,我找到了解决方案。
我非常接近 - 让泛型OptionsTransformer
强制递归子类型也实现接口 - 即使它做到了,类型也会被删除(出于我不知道的原因)然后它没有实现类型。
解决方案是让OptionsTransformer
泛型不扩展子类型。
最终的解决方案如下所示,为清晰起见重命名了类:
//can be any class!
class AnyClass<T> {
constructor(item: T) {
console.log(typeof item)
}
}
//just using string here, but can be any type(s).
interface RecursiveInterface {
[key: string]: string | RecursiveInterface
}
//fix is here: T no longer extends Recursive interface.
//I'm not 100% sure why, but it works. Any sub object will be
//"duck typed" into recursive interface, so you can nest as deeply
//as you desire.
type RecursiveInterfaceWrappedWithClass<T> = {
[key in keyof T]: T[key] extends RecursiveInterface ? RecursiveInterfaceWrappedWithClass<T[key]> : AnyClass<T[key]>
}
//example: interface with nested object. Only go 1 deep in this example,
//but you can nest as far as you'd like.
interface MyObject {
foo: string
bar: {
baz: string
}
}
//here's the wrapper.
const myObjectWrapped: RecursiveInterfaceWrappedWithClass<MyObject> = {
foo: new AnyClass('string'),
bar: {
baz: new AnyClass('hi')
}
}
推荐阅读
- javascript - 如何从jsgrid数据制作一个csv文件
- node.js - 无法使用打字稿快速访问 /login 路由
- time - PHPExcel - 如何将单元格设置为仅包含时间(不包含日期)
- xml - XSLT 1.0 中从平面到嵌套 XML 的转换
- java - 从测试类以外的类执行存根时出现 UnfinishedStubbingException
- sql - 如何使用更新函数将字段中的所有变量更改为 3 个值。替换不起作用,更新似乎接近了?SQL 大查询
- postgresql - 在 docker postgres 容器中导入 postgres 数据库
- json - 使用 Newtonsoft 解析 Elasticsearch 中的 JSON
- java - Smack android 启用 xep-0115 缓存
- angularjs - Ng-repeat 未在 AngularJS 的选择选项中显示任何数据