匹配 Typescript 中的通用嵌套对象类型

Match generic nested object type in Typescript

假设我有一个嵌套对象类型来定义,比方说,一个用户:

type TestUser = {
    name: string;
    email: string;
    houses: {
        address: string;
        rooms: {
            floor: number;
            color: string;
            connectedTo: {
                id: number;
            }[];
        }[];
        doors: {
            location: number;
            size: number;
        }[];
    }[];
    tasks: {
        description: string;
        people: {
            nickname: string;
        }[];
    }[];
}

我想定义一个可以接受这种类型的泛型函数,然后定义该类型的嵌套属性;例如,在这种情况下,这可能看起来像:

const houseSpecification = [{
    name: 'houses',
    children: [
        // (not all subproperties of the type need to be defined, e.g. note how the 
        // 'tasks' branch is missing altogether)
        {
            name: 'rooms',
            children: [
                {
                    name: 'id',
                },
            ],
        },
        {
            name: 'doors',
        },
    ],
}],

在每个分支中,'name' 属性 指定 属性 父对象的名称,它是一个 对象数组 类型。然后将数组中对象的类型作为 'children' 属性.

中条目的 'name' 的来源

我正在寻找对任何给定对象类型采用这种格式的某些函数中这种规范类型的定义,例如function myFunction<T extends object>(spec: Spec<T>[])Spec 的类型使得 myFunction<TestUser>houseSpecification 作为其参数。

我有一些我认为应该有用的东西,但由于某种原因似乎没有正确地递归:

// extracts the type of an array, e.g. ArrayType<({ id: string })[]> -> { id: string }
export type ArrayType<T> = T extends (infer U)[] ? U : never;

// gives a union type of all keys in an object that yield an array of objects, 
// e.g. ObjectArrKeys<{ id: string, data: object[], users: object[] }> -> 'data' | 'users'
export type ObjectArrKeys<T> = {
    [K in keyof T]: T[K] extends object[] ? K : never;
}[keyof T];

// used as `function myFunction<T extends object>(spec: Spec<T, ObjectArrKeys<T>>[])`
export type Spec<T extends object, Q extends ObjectArrKeys<T>> = {
    name: Q;
    children?: Spec<
            ArrayType<T[Q]>,
            ObjectArrKeys<ArrayType<T[Q]>>
    >[]
}

我已经测试了两个实用程序,ArrayTypeObjectArrKeys,它们似乎工作正常。然而,Typescript 似乎不喜欢 Spec 中的递归,抱怨 ArrayType<T[Q]> 与类型 object 不匹配,尽管事实上 Q 只能表示生成对象数组的 T 键。

有没有更简单的方法来获得我的最终结果?为什么 Typescript 不能正确推断这些类型并声称它不够具体?

谢谢。

如果我正确理解了问题,这应该可以解决问题:

type Spec<T> = {
  [key in ObjectArrKeys<T>]: {
    name: key, children?: T[key] extends (infer I)[] ? Spec<I> : never
  }
}[ObjectArrKeys<T>][]

首先,由于houseSpecification和嵌套的children属性都是数组,所以我也让Specreturn为数组类型。我遍历所有具有数组属性的键,并构建所有可能的 namechildren 组合的并集。对于每次迭代,我然后采用推断的元素类型并递归调用 Spec.

在将嵌套对象传递到递归 Spec 调用之前,我再次检查 T[key] 是否确实 extends (infer I)[]。我认为这些信息之前丢失了,TypeScript 并不知道 T[key]any[] 类型,即使 ObjectArrKeys 过滤了所有数组键。

Playground