Typescript实现嵌套对象带对应类型的深度keyof,适配MongoDB点表示法补全
实现方案
以下是完整的类型实现,完全覆盖你提出的所有需求场景:
// 基础工具:判断两个类型是否完全相等 type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false; // 提取对象中非readonly修饰的属性键 type WritableKeys<T> = { [K in keyof T]-?: Equal<{ [P in K]: T[K] }, { -readonly [P in K]: T[K] }> extends true ? K : never }[keyof T]; // 路径拼接工具,生成点表示法的键 type JoinPath<P extends string, K extends string | number> = P extends '' ? `${K}` : `${P}.${K}`; // 递归生成点表示法的键值对集合 type DotNotationEntries<T, StopTypes = Date | RegExp | boolean | string | number | null | undefined, P extends string = ''> = // 命中终止类型直接返回空,不展开内部属性 T extends StopTypes ? {} : // 处理数组类型 T extends Array<infer U> ? DotNotationEntries<U, StopTypes, JoinPath<P, number>> : // 处理元组类型 T extends [any, ...any[]] ? { [K in keyof T as JoinPath<P, K & (string | number)>]: T[K] } : // 处理普通对象类型 T extends object ? { [K in WritableKeys<T> as JoinPath<P, K & string>]: T[K] } & { [K in WritableKeys<T> as keyof DotNotationEntries<T[K], StopTypes, JoinPath<P, K & string>>]: DotNotationEntries<T[K], StopTypes, JoinPath<P, K & string>>[keyof DotNotationEntries<T[K], StopTypes, JoinPath<P, K & string>>] } : {}; // 对外暴露的最终工具类型 type MongoFieldType<T, StopTypes = Date | RegExp | boolean | string | number | null | undefined> = // 保留原对象的可写属性 { [K in WritableKeys<T>]: T[K] } // 合并点表示法的键值对,过滤掉和原属性重复的根级键 & Omit<DotNotationEntries<T, StopTypes>, keyof T>;
使用示例验证
基础嵌套对象场景
interface IPerson { name: string; age: number; contact: { address: string; visitDate: Date; } } type TPerson = MongoFieldType<IPerson>; // 生成结果: // { // name: string; // age: number; // contact: { address: string; visitDate: Date; }; // "contact.address": string; // "contact.visitDate": Date; // }
数组索引签名场景
interface IPerson1 { contact: { address: string; visitDate: Date; }[] } type TPerson1 = MongoFieldType<IPerson1>; // 生成结果: // { // contact: { address: string; visitDate: Date; }[]; // [x: `contact.${number}.address`]: string; // [x: `contact.${number}.visitDate`]: Date; // }
元组场景
interface IPerson2 { contact: [string, Date] } type TPerson2 = MongoFieldType<IPerson2>; // 生成结果: // { // contact: [string, Date]; // "contact.0": string; // "contact.1": Date; // }
Readonly属性过滤场景
interface IPerson3 { readonly _id: string; age: number; readonly _created_date: Date; } type TPerson3 = MongoFieldType<IPerson3>; // 生成结果: // { // age: number; // }
可选属性保留场景
interface IPerson4 { contact: { address: string; visitDate?: Date; }[]; } type TPerson4 = MongoFieldType<IPerson4>; // 生成结果: // { // contact: { address: string; visitDate?: Date; }[]; // [x: `contact.${number}.address`]: string; // [x: `contact.${number}.visitDate`]?: Date; // }
交叉类型场景
interface IPerson5 { contact: { address: string; } & { visitDate: Date; } } type TPerson5 = MongoFieldType<IPerson5>; // 生成结果: // { // contact: { address: string; } & { visitDate: Date; }; // "contact.address": string; // "contact.visitDate": Date; // }
自定义终止类型场景
默认终止类型已经包含Date、string、number等常用基础类型,无需额外配置即可避免展开内置类型的内部属性,也可以手动传入第二个泛型参数自定义终止类型:
// 示例:新增Buffer为终止类型 type CustomStopTypes = Date | RegExp | boolean | string | number | null | undefined | Buffer; type TPersonCustom = MongoFieldType<IPerson, CustomStopTypes>;
内容的提问来源于stack exchange,提问作者Val
相关产品推荐
相关产品推荐

