TypeScript递归实现嵌套对象至少一个属性必填的方案咨询
递归实现TypeScript嵌套对象至少一个属性必填
问题分析
需要限制包含嵌套子对象的TypeScript类型至少填写一个属性,单层的AtLeastOne类型可以满足平面对象需求,但递归版本无法生效;同时顶层可选属性(如GlobalOptions中的draggableOptions)若传入则必须满足至少一个属性的要求。
解决方案
核心类型实现
先定义辅助类型判断对象类型,再结合递归转换与单层AtLeastOne实现递归版本:
// 判断是否为需要递归处理的非基础对象(排除数组、函数、内置对象等) type IsObject<T> = T extends object ? T extends Array<any> | Function | Date | RegExp ? false : true : false; // 单层对象至少一个属性必填的实现 type AtLeastOne<T> = { [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>; }[keyof T]; // 递归转换对象的每个属性:将嵌套对象类型替换为递归的AtLeastOne type RecursiveObjectTransform<T> = { [K in keyof T]: IsObject<T[K]> extends true ? RecursiveAtLeastOne<T[K]> : T[K]; }; // 递归版至少一个属性:顶层必须至少有一个属性,嵌套对象若传入也必须满足至少一个属性 type RecursiveAtLeastOne<T> = AtLeastOne<RecursiveObjectTransform<T>>;
应用到你的类型定义
1. 处理DraggableOptions与DroppableOptions
直接用RecursiveAtLeastOne包裹原类型即可:
type DraggableOptions = { identifier?: { id?: string; type?: string; }; modifiers?: { dropEffect?: "copy" | "move"; }; data?: any; } export type DroppableOptions = { identifier: { id?: string; type?: string[]; }; modifiers?: { highlight?: { on: "dragmove" | "dragover"; class: string; }; }; }; // 转换为递归至少一个属性的类型 type ValidDraggable = RecursiveAtLeastOne<DraggableOptions>; type ValidDroppable = RecursiveAtLeastOne<DroppableOptions>;
此时:
- 有效示例:
{ data: "test" }、{ identifier: { id: "1" } }、{ modifiers: { dropEffect: "move" } } - 无效示例:
{}、{ identifier: {} }、{ modifiers: {} }
2. 处理GlobalOptions
顶层可选属性若传入则必须满足递归至少一个属性的要求,直接替换类型即可:
export type GlobalOptions = { draggableOptions?: ValidDraggable; droppableOptions?: ValidDroppable; };
为什么之前的递归版本无效
你之前的递归实现仅对每个嵌套属性应用了AtLeastOne,但未保证顶层对象至少有一个属性被填写,同时也没有处理“可选属性仅在传入时需要满足必填规则”的逻辑,导致空对象仍能通过类型校验。
内容的提问来源于stack exchange,提问作者GokuNik
相关产品推荐
相关产品推荐

