递归删除不必要的容器对象的函数的类型



服务器返回的对象可能包含容器属性。

想象一个像这样的物体

{
attr1: string,
attr2: number,
attr3: {
uselessArg1: ...,
uselessArg2: ...,
uselessArg3: ...,
content: [subVal1, subVal2, ...]
}
}

我有一个函数,可以抓取那个对象并像这样返回:

{
attr1: string,
attr2: number,
attr3: [subVal1, subVal2, ...]
}

现在,真正的问题是,任何对象都可能有一个属性,该属性的对象类型带有无用的键和content子属性,包括redundant数组中的对象

因此,我们可以想象,任何给定的对象都继承自这个接口:

interface MaybeHasContent {
[key:string]?: {
uselessArg1: ...,
uselessArg2: ...,
uselessArg3: ...,
content:this[]
} 
}

我的实用程序函数首先验证对象中的任何键是否有content子键,如果找到,则将content子键的值提升到其父键,这也会清除所有无用的属性。然后,它继续对当前工作对象的任何子对象以及content数组内的任何对象调用自己。它基本上贯穿对象的所有分支,包括具有对象阵列的分支,并将一个非常特定的密钥提升一级

我该如何为这个怪物编写界面?我想让TS明白,如果我有一个可以写myObj.val1.redundant[0].name的对象,那么在通过我的实用程序函数传递它之后,这将是一个合法的调用:myObj.val1[0].name

这里有一种可能的方法:

declare function clean<T>(t: T): Clean<T>;
type Clean<T> = T extends object ? (
T extends { content: infer U } ?
Clean<U> : { [K in keyof T]: Clean<T[K]> }
) : T;

函数采用泛型类型T的值t,并返回类型Clean<T>的值。

而CCD_ 11是表示CCD_ 12的作用的递归条件类型。如果T是非基元object类型,那么我们检查是否具有content属性。如果是,我们提取该属性(使用条件类型inference(,并将其作为新的类型参数传递给Clean。如果不是,那么我们将对象类型T映射到每个属性值都应用了Clean的版本。(注意,这也涉及数组类型,因为数组和元组上的映射类型变成了数组和元组。(最后,如果T是基元类型,我们只返回T


让我们测试一下:

const cleaned = clean({
attr1: "abc",
attr2: 123,
attr3: {
uselessArg1: 1,
uselessArg2: 2,
uselessArg3: 3,
content: [
{ a: 1, b: 2, c: { content: "a", x: 1 } },
{ a: 3, b: 4, c: { content: "b", x: 2 } }
]
}
});
/* const cleaned: {
attr1: string;
attr2: number;
attr3: {
a: number;
b: number;
c: string;
}[];
} */

看起来不错。CCD_ 22的类型与具有所有CCD_;"吊起来";。因此attr3不具有uselessArg属性。它只是一个数组。该数组的元素有一个c属性,它只是string,因为输入数组的c属性本身就有一个值为stringcontent属性。


此类递归类型实用程序总是存在边缘情况。例如,如果你试图清理一个病态的递归数据结构,那将是一个坏主意:

interface ContentOuroboros {
content: ContentOuroboros
}
type OhNoes = Clean<ContentOuroboros> // error!
// Type instantiation is excessively deep and possibly infinite.

我想你不太可能这么做,但我很难提前想到所有可能的陷阱。因此,一定要对照您的用例进行测试,并在必要时进行调整。

游乐场链接到代码

相关内容

  • 没有找到相关文章

最新更新