类型操作:通过递归将可选空类型映射到另一个可选空类型



需要帮助编写以下递归代码的简洁形式

我要找的是如果给定类型为T1 | null | undefined我想把它转换成T2 | null | undefined

T1可以是可选的或可空的,即我们可以有T1 | nullT1 | undefined

type DateJson = {
__type: 'Date';
iso: string;
}
interface Pointer {
__type: 'Pointer';
className: string;
objectId: string;
}
interface ParseObject {
__type: 'Object';
className: string;
}
type ParseAttributes<T> = JsonToAttributeTypes<JsonToAttributeTypes<T, Pointer, ParseObject>, DateJson, Date>
type JsonToAttributeTypes<T, S, R> = {
[k in keyof T]: T[k] extends S
? R
: T[k] extends S | null
? R | null
: T[k] extends S | undefined
? R | undefined
: T[k] extends S | null | undefined
? R | null | undefined
: T[k] extends Record<string, unknown> ? JsonToAttributeTypes<T[k], S, R> : T[k]

输入这个输入涵盖了大部分的边缘情况

type Test = {
user: Pointer;
employee?: {
pointer?: Pointer | null;
name: string;
company: string;
};
date?: DateJson;
other: {
date: DateJson | null;
testKey: number;
};
nested: {
date: DateJson;
deep: {
pointer: Pointer | null
another: {
test?: string;
date?: DateJson | null
}
}
}
}

这是预期的输出。keep只希望递归地将一种类型更改为另一种类型(主要是3到4层)。可能有更多类型需要转换

{
user: ParseObject;
employee?: {
pointer?: ParseObject | null;
name: string;
company: string;
};
date?: Date;
other: {
date: Date | null;
testKey: number;
};
nested: {
date: Date;
deep: {
pointer: ParseObject | null
another: {
test?: string;
date?: Date | null
}
}
}
}

也,是否有可能重写JsonToAttributeTypes<JsonToAttributeTypes<T, Pointer, ParseObject>, DateJson, Date>与类型和循环数组?

而不是将T | null | undefined案例与T | null,T | undefined和普通T案例分开,让我们概括一下您正在寻找的内容:如果S出现在T中作为联合类型的任何成员,您希望JsonToAttributeTypes<T, S, R>R替换T中的S。所以如果S变成了R,那么S | 123应该变成了R | 123。如果T是一个对象类型,您还需要在T的任何键或子键中递归地用R替换S

有一种写法:

type JsonToAttributeTypes<T, S, R> =
T extends S ? R : 
T extends object ? { [K in keyof T]: JsonToAttributeTypes<T[K], S, R> } : 
T;

T extends S ? R : ...类型是一个分布式条件类型,它自动地将T中的S替换为R

如果T的任何联合成员不是S,那么我们检查它是否是一个非原语类型(T extends object,使用object类型,这可能会或可能不会对您的linter造成问题,即使我不认为这应该是一个问题)。如果它不是原始的,我们递归到它并对每个属性执行JsonToAttributeTypes。否则,不加更改地返回T的联合成员。


我们可以用一个例子来测试它:用boolean替换string在某些对象类型:

type X = JsonToAttributeTypes<
{ a?: string, b: { c: string | number }, c?: { d: string } },
string,
boolean
>
/* type X = {
a?: boolean | undefined;
b: {
c: number | boolean;
};
c?: {
d: boolean;
} | undefined;
} */

看起来不错。并且您可以验证您的示例也按照期望的方式运行。


当然,对于JsonToAttributeTypes<T, S, R>的任何定义,都可能存在期望输出与实际输出不匹配的边缘或角落情况。如果T是包括S的交集类型(因此S & X可能不会变成R & X),或者T是函数类型等,当前的定义可能不会做你想要的。人们可能会调整定义来解释或部分解释其中的一些,但关键是确保它做您想要的事情的唯一方法是针对广泛的潜在用例进行广泛的测试。小心!

Playground链接到代码

最新更新