如何对映射对象属性的TypeScript函数进行类型注释? 出身背景
假设我想从这个JSON加载一个对象:如何对映射对象属性的TypeScript函数进行类型注释? 出身背景,typescript,Typescript,假设我想从这个JSON加载一个对象: { “dateStringA”:“2019-01-02T03:04:05”, “dateStringB”:“2019-01-03T04:05:06”, “非日期字符串”:“foobar”, “someNumber”:123 } 因此,两个属性dateStringA和dateStringB实际上应该是Date类型,但由于JSON不知道类型Date,因此它是一个字符串,需要转换。因此,一个选项可能是编写一个简单的映射函数,在普通的旧JavaScript中转换如
{
“dateStringA”:“2019-01-02T03:04:05”,
“dateStringB”:“2019-01-03T04:05:06”,
“非日期字符串”:“foobar”,
“someNumber”:123
}
因此,两个属性dateStringA
和dateStringB
实际上应该是Date
类型,但由于JSON不知道类型Date
,因此它是一个字符串,需要转换。因此,一个选项可能是编写一个简单的映射函数,在普通的旧JavaScript中转换如下属性:
函数映射属性(obj、映射器、属性){
properties.forEach(函数(属性){
obj[property]=映射器(obj[property]);
});
返回obj;
}
var usefulObject=mapProperties(
jsonObject,
函数(val){返回新日期(val);},
“dateStringA”,
“dateStringB”
);
问题
上面的工作很好,但是现在我想在TypeScript中做同样的事情,当然我想添加尽可能多的类型检查。因此,在最好的情况下,我希望得到以下结果:
//设置
常量值={dateStringA:'2019-01-02T03:04:05',dateStringB:'2019-01-03T04:05:06',非日期字符串:“”,someNumber:123};
const result=mapProperties(值,(val:string):Date=>newdate(val),'dateStringA','dateStringB');
//---测试---
//dateStringA和dateStringB现在应该是日期:
result.dateStringA.substr;//应引发编译错误-substr在类型日期不存在
result.dateStringB.substr;//应引发编译错误-substr在类型日期不存在
result.dateStringA.getDate;//应该可以
result.dateStringB.getDate;//应该可以
//非日期字符串仍然是字符串
result.nonDateString.substr;//应该可以
result.nonDateString.getDate;//应引发编译错误-类型字符串上不存在getDate
//someNumber仍然是一个数字
result.someNumber.toFixed;//应该可以
//无法对不存在的属性调用:
mapProperties(值“doesNotExist”);//应该抛出编译错误
//无法对非字符串类型的属性调用:
mapProperties(值“someNumber”);//应该抛出编译错误
到目前为止,我所尝试的:
这是我自己得到的最好的:
type PropertyNamesByType={[K in keyof O]:O[K]扩展了T?K:never}[keyof O];
类型OverwriteType=拾取和记录;
函数映射属性<
包装器类型,
WRAPPER_键扩展(WRAPPER_类型和PropertyNamesByType的键),
旧式,
新型
>(obj:WRAPPER_类型,
映射器:(值:旧类型)=>新类型,
…属性:包装器_键[]
):覆盖类型{
常量结果:OverwriteType=obj;
properties.forEach(key=>{
(结果[key])=映射器(obj[key]);
});
返回结果;
}
这似乎确实有效,但有两个奇怪之处:
行WRAPPER\u key扩展(keyof WRAPPER\u TYPE&PropertyNamesByType)
。我认为它应该只使用WRAPPER\u KEYS扩展PropertyNamesByType
,而不使用&keyof WRAPPER\u TYPE
,因为后者实际上不应该添加任何附加信息(我发现这完全是偶然的)。然而,如果我省略这个,TypeScript的行为就好像所有字符串属性都被转换了一样。那里发生了什么奇迹
在(result[key])=mapper(obj[key])行中代码>我需要这两个
-cast。有没有办法摆脱这些
可以映射属性是否显示在键列表中,然后使用转换类型或原始类型:
// (just the type signature)
declare function mapProperties<Json, SourceType, TargetType, P extends keyof Json>(
obj: Json,
converter: (value: SourceType) => TargetType,
...keys: P[]): { [K in keyof Json]: K extends P ? TargetType : Json[K] }
//(仅限于类型签名)
声明函数映射属性(
obj:Json,
转换器:(值:SourceType)=>TargetType,
…keys:P[]:{[K in keyof Json]:K扩展了P?TargetType:Json[K]}
助手类型:
type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U;
type Morphism<T = any, U = any> = (argument: T) => U;
很酷,我只需要稍微编辑一下函数就可以了。现在,我尝试创建一些如下的默认包装:constconvertdateproperties=(obj:T,…propertyNames:K[])=>mapProperties(obj,(val:string)=>newdate(val),propertyNames)代码>。但这当然不起作用,因为我没有指定T[K]必须是字符串。您是否也知道此问题的解决方案?您需要通过在T
上添加约束来确保值(T[K]
)的类型为string
。执行此操作:const-convertDateProperties=(obj:T,…propertyNames:K[])=>mapProperties(obj,(val:string)=>new-Date(val),…propertyNames)代码>
const transform = <T, U extends Morphism<T[K]>, K extends keyof T>(source: T, mappingFn: U, ...properties: K[]) =>
(Object.entries(source))
.reduce(
(accumulator, [key, value]) => {
const newValue =
properties.includes(key as K)
? mappingFn(value)
: value
return ({ ...accumulator, [key]: newValue })
},
{} as Overwrite<T, Record<K, ReturnType<U>>>
);
const source = {
dateStringA: "2019-01-02T03:04:05",
dateStringB: "2019-01-03T04:05:06",
nonDateString: "foobar",
someNumber: 123
}
const toDate = (date: string) => new Date(date);
console.log(
transform(source, toDate, 'dateStringA', 'dateStringB')
)