递归扁平化对象指定属性:移除sys、metadata并展开fields类型
解决TypeScript递归移除指定属性并扁平化fields的类型工具编写问题
需求说明
编写一个TypeScript类型工具,实现两个核心功能:
- 递归移除对象中的
sys、metadata属性 - 将对象中的
fields属性扁平化(即把fields内部的属性直接提升到当前层级)
输入类型
{ sys: { id: string; }; metadata: { author: string; }; fields: { name: string; age: number; father: { sys: { id: string; }; metadata: { author: string; }; name: string; }, siblings: { sys: { id: string; }; metadata: { author: string; }; name: string; }[] }; }
期望输出类型
{ name: string; age: number; father: { name: string; }; siblings: { name: string; }[]; }
当前思路瓶颈
原思路尝试通过映射类型判断键是否为sys/metadata来返回never,但无法正确处理fields的扁平化逻辑,也无法将扁平化后的属性与原对象(移除指定属性后)合并。现有代码片段:
type Helper<T extends object> = { [K in keyof T]: K extends "sys" | "metadata" ? never : K extends "fields" ? ... : T[K] extends Array<infer I> ? I extends object ? Helper<I> : never : T[K] extends object ? Helper<T[K]> : T[K]; };
解决方案
我们可以拆分逻辑为移除指定属性和扁平化+递归处理两个部分,通过组合多个辅助类型实现需求:
完整类型代码
// 移除对象中的sys和metadata属性 type RemoveSysMetadata<T> = Omit<T, "sys" | "metadata">; // 递归处理扁平化fields和嵌套对象/数组 type FlattenFieldsAndRecurse<T> = T extends object ? T extends Array<infer U> ? // 处理数组:递归处理每个元素 Array<FlattenFieldsAndRecurse<RemoveSysMetadata<U>>> : // 处理对象:先移除sys/metadata,再遍历键 { [K in keyof RemoveSysMetadata<T>]: // 若当前值是对象,先递归处理 RemoveSysMetadata<T>[K] extends object ? FlattenFieldsAndRecurse<RemoveSysMetadata<T>[K]> extends infer ProcessedValue ? // 如果当前键是fields,直接返回处理后的值(扁平化) K extends "fields" ? ProcessedValue : ProcessedValue : never : // 非对象类型直接保留 RemoveSysMetadata<T>[K]; } : // 非对象类型直接返回 T; // 主类型工具 type CleanObject<T> = FlattenFieldsAndRecurse<T>;
代码解释
- RemoveSysMetadata:利用TypeScript内置的
Omit类型,直接移除对象中的sys和metadata属性,比在映射类型中逐个判断更简洁高效。 - FlattenFieldsAndRecurse:
- 首先判断输入是否为数组:如果是,递归处理数组中的每个元素(先移除
sys/metadata,再递归)。 - 如果是对象:先移除
sys/metadata,然后遍历每个键值对:- 若值是对象,先递归处理该值;如果当前键是
fields,则直接将递归处理后的值提升到当前层级(实现扁平化)。 - 若值不是对象,直接保留原类型。
- 若值是对象,先递归处理该值;如果当前键是
- 首先判断输入是否为数组:如果是,递归处理数组中的每个元素(先移除
- CleanObject:对外暴露的主类型,调用上述辅助类型完成完整处理。
验证效果
// 定义输入类型 type InputType = { sys: { id: string }; metadata: { author: string }; fields: { name: string; age: number; father: { sys: { id: string }; metadata: { author: string }; name: string; }, siblings: { sys: { id: string }; metadata: { author: string }; name: string; }[] }; }; // 生成处理后的类型 type OutputType = CleanObject<InputType>; // 定义期望输出类型 type ExpectedOutput = { name: string; age: number; father: { name: string }; siblings: { name: string }[]; }; // 验证类型是否匹配 type IsMatch = OutputType extends ExpectedOutput ? true : false; // 结果为true
内容的提问来源于stack exchange,提问作者Johnny Kontrolletti
相关产品推荐
相关产品推荐

