TypeScript递归拆解对象fields键时如何正确定义返回值类型
类型定义与函数改造方案
第一步:实现递归展开类型
通过TypeScript条件递归类型,动态推导展开fields后的对象结构:
type FlattenContentful<T> = // 基础类型/数组直接返回原类型 T extends Record<string, any> ? // 排除原对象的fields键,合并fields递归展开后的类型 (Omit<T, 'fields'> & (T extends { fields: infer F } ? FlattenContentful<F> : {})) extends infer Merged ? // 递归处理合并后对象的所有属性值 { [K in keyof Merged]: FlattenContentful<Merged[K]> } : never : T;
第二步:给函数添加泛型绑定
改造原函数,将输入类型和返回类型做关联,同时补充边界判断优化:
type TContentfulModel = { fields?: undefined | Record<string, TContentfulModel>; }; function contentfulMapper<T extends Record<string, any>>(obj: T): FlattenContentful<T> { let out: Record<string, unknown> = {}; Object.keys(obj).forEach((key) => { const value = obj[key]; if (key === 'fields') { const mappedField = contentfulMapper(value as Record<string, any>); out = { ...out, ...mappedField }; } else { out[key] = // 补充null判断,避免null值进入递归逻辑报错 typeof value === 'object' && value !== null && !Array.isArray(value) ? contentfulMapper(value as Record<string, any>) : value; } }); return out as FlattenContentful<T>; }
效果验证
使用你给出的示例测试,可获得精准的类型提示:
const givenObj = { a: "sdf", w: { a2: 343, a3: true, fields: { a4: "ll", q: { fields: { a5: [2, 4, "lk"] }, }, }, }, }; const mapped = contentfulMapper(givenObj); // TS自动推导mapped类型为: // { // a: string; // w: { // a2: number; // a3: boolean; // a4: string; // q: { // a5: (number | string)[]; // }; // }; // } // 访问mapped.w.a4、mapped.w.q.a5等属性都可获得完整类型提示
内容的提问来源于stack exchange,提问作者Zak Laberg
相关产品推荐
相关产品推荐

