假设我要从此JSON加载对象:
{
"dateStringA": "2019-01-02T03:04:05",
"dateStringB": "2019-01-03T04:05:06",
"nonDateString": "foobar",
"someNumber": 123
}
因此,两个属性dateStringA
和dateStringB
实际上应该是类型Date
,但是由于JSON不知道类型Date
,因此它是string
并且需要进行转换。因此,一种选择可能是编写一个简单的映射函数,该函数在普通的旧JavaScript中转换如下所示的属性:
function mapProperties(obj, mapper, properties) {
properties.forEach(function(property) {
obj[property] = mapper(obj[property]);
});
return obj;
}
var usefulObject = mapProperties(
jsonObject,
function(val) {return new Date(val);},
'dateStringA',
'dateStringB'
);
上面的方法很好,但是现在我想在TypeScript中做同样的事情,当然我想添加尽可能多的类型检查。所以在最好的情况下,我想得到以下结果:
// setup
const value = {dateStringA: '2019-01-02T03:04:05', dateStringB: '2019-01-03T04:05:06', nonDateString: '', someNumber: 123};
const result = mapProperties(value, (val: string): Date => new Date(val), 'dateStringA', 'dateStringB');
// --- TEST ---
// dateStringA & dateStringB should be dates now:
result.dateStringA.substr; // should throw compile error - substr does not exist on type Date
result.dateStringB.substr; // should throw compile error - substr does not exist on type Date
result.dateStringA.getDate; // should be OK
result.dateStringB.getDate; // should be OK
// nonDateString is still a string
result.nonDateString.substr; // should be OK
result.nonDateString.getDate; // should throw compile error - getDate does not exist on type string
// someNumber is still a number
result.someNumber.toFixed; // should be OK
// call not possible on properties that do not exist:
mapProperties(value, 'doesNotExist'); // should throw compile error
// call not possible on properties not of type string:
mapProperties(value, 'someNumber'); // should throw compile error
这是我一个人得到的最好成绩:
type PropertyNamesByType<O, T> = { [K in keyof O]: O[K] extends T ? K : never }[keyof O];
type OverwriteType<T, K extends keyof T, N> = Pick<T, Exclude<keyof T, K>> & Record<K, N>;
function mapProperties<
WRAPPER_TYPE,
WRAPPER_KEYS extends (keyof WRAPPER_TYPE & PropertyNamesByType<WRAPPER_TYPE, OLD_TYPE>),
OLD_TYPE,
NEW_TYPE
>(obj: WRAPPER_TYPE,
mapper: (value: OLD_TYPE) => NEW_TYPE,
...properties: WRAPPER_KEYS[]
): OverwriteType<WRAPPER_TYPE, WRAPPER_KEYS, NEW_TYPE> {
const result: OverwriteType<WRAPPER_TYPE, WRAPPER_KEYS, NEW_TYPE> = <any>obj;
properties.forEach(key => {
(<any>result[key]) = mapper(<any>obj[key]);
});
return result;
}
这实际上似乎可行,但是有两个奇怪之处:</ p>
WRAPPER_KEYS extends (keyof WRAPPER_TYPE & PropertyNamesByType<WRAPPER_TYPE, OLD_TYPE>)
行。我认为它应该仅适用于WRAPPER_KEYS extends PropertyNamesByType<WRAPPER_TYPE, OLD_TYPE>
,而不能使用& keyof WRAPPER_TYPE
,因为后者实际上不应添加任何其他信息(我很偶然地发现了这一点)。但是,如果我忽略了这一点,TypeScript的行为就像转换了所有字符串属性一样。那里发生了什么魔术?(<any>result[key]) = mapper(<any>obj[key]);
行中,我需要那两个<any>
广播。有什么办法可以摆脱这些?答案 0 :(得分:1)
助手类型:
type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U;
type Morphism<T = any, U = any> = (argument: T) => U;
示例实现:
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>>>
);
备注:
U extends Morphism<T[K]>
确保转换器仅接受properties
(用T[K]
表示)的值。ReturnType
需要TypeScript 2.8或更高版本
用法:
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')
)
答案 1 :(得分:0)
您可以映射属性是否出现在键列表中,然后使用转换后的类型还是原始类型:
// (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] }