有两个问题。
TransformedValue
逻辑到
E
参数不是
Transform
E
是数组类型(仅更改元素类型)或对象类型(并转换属性名),如果两者都不是,则不需要对其进行处理(它可能是一个基元,我们不应该映射它)。现在既然你申请了
变换
到
E
结果是原语将被重命名过程损坏。
因为类型别名不能是递归的,所以我们可以定义一个从数组派生的接口,它将应用于
对于其类型参数:
type TransformedValue<T> =
T extends Array<infer E> ? TransformedArray<E> :
T extends object ? Transform<T> :
T;
interface TransformedArray<T> extends Array<TransformedValue<T>>{}
第二个问题与这样一个事实有关,即如果一个接口具有可选属性,并且该接口被放入同态映射类型,那么成员的可选性将被保留,从而导致
T[keyof T]
undefined
KeyValueTupleToObject
. 最简单的解决方案是显式地去除可选性
type MapKeys<T, M extends Record<string, string>> = KeyValueTupleToObject<
ValueOf<{
[K in keyof T]-?: [K extends keyof M ? M[K] : K, T[K]]
}>
>;
把这一切放在一起,它应该起作用:
link
编辑
一个使类型更具可读性的解决方案可以使用另一个@jcalz答案将并集转换为交集(
this one
).
同时,下面的解决方案将保持类型的可选性,
readonly
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type MapKeysHelper<T, K extends keyof T, M extends Record<string, string>> = K extends keyof M ? (
Pick<T, K> extends Required<Pick<T, K>> ?
{ [P in M[K]]: T[K] } :
{ [P in M[K]]?: T[K] }
) : {
[P in K]: T[P]
}
type Id<T> = { [P in keyof T]: T[P] }
type MapKeys<T, M extends Record<string, string>> = Id<UnionToIntersection<MapKeysHelper<T, keyof T, M>>>;
export type Transform<T> = MapKeys<
{ [P in keyof T]: TransformedValue<Exclude<T[P], undefined>> },
KeyMapper
>;
type TransformedValue<T> =
T extends Array<infer E> ? TransformedArray<E> :
T extends object ? Transform<T> :
T;
interface TransformedArray<T> extends Array<TransformedValue<T>> { }
type KeyMapper = {
foo: 'foofoo';
bar: 'barbar';
};
interface OnlyScalars {
foo: string;
bar: number;
baz: {
foo: string;
bar: number;
}
}
export type TransformOnlyScalars = Transform<OnlyScalars>;
// If you hover you see:
// {
// foofoo: string;
// barbar: number;
// baz: Id<{
// foofoo: string;
// } & {
// barbar: number;
// }>;
// }
interface TestArray {
foo: string[];
bar: number;
}
export type TransformArray = Transform<TestArray>;
// If you hover you see:
// {
// foofoo: TransformedArray<string>;
// barbar: number;
// }
interface TestOptional {
foo?: string;
bar: number;
baz: {
foo: string;
bar: number;
}
}
export type TransformOptional = Transform<TestOptional>;
// If you hover you see:
// {
// foofoo?: string | undefined;
// barbar: number;
// baz: Id<{
// foofoo: string;
// } & {
// barbar: number;
// }>;
// }