You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript 4.7.4+中Flatten类型的PrefixedRecord失效求助

TypeScript 4.7.4+ 下Flatten类型中PrefixedRecord索引异常的修复方案

从TypeScript 4.7.4版本起,自定义的Flatten类型里的PrefixedRecord出现异常,无法通过指定的带前缀键进行索引。原类型定义如下:

export declare type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
declare type StringKeysOf<T> = Extract<keyof T, string>;
declare type PrefixedRecord<Prefix extends string, Obj extends Record<string, any>> = {
    [Key in StringKeysOf<Obj> as `${Prefix}.${Key}`]: Obj[Key];
};
declare type NonObjectKeysOf<T> = {
    [K in keyof T]: T[K] extends Array<any> ? K : T[K] extends object ? never : K;
}[keyof T];
declare type ObjectKeysOf<Obj> = Exclude<keyof Obj, NonObjectKeysOf<Obj>>;
export declare type Flatten<T, K extends StringKeysOf<T> = StringKeysOf<T>> = UnionToIntersection<K extends ObjectKeysOf<T> ? PrefixedRecord<K, T[K]> : {
    [k in K]: T[K];
}>;

问题原因分析

原Flatten类型通过泛型参数K的联合类型分发特性,逐个处理每个键后再转交叉类型。但TypeScript 4.7.4+版本对条件类型分发后的交叉类型键推导逻辑做了调整,导致模板字符串生成的带前缀键无法被识别为可索引的字面量类型,最终无法通过键直接访问对应属性。

另外原NonObjectKeysOf将数组归为非对象键,若需要扁平化数组结构,这一判断不符合预期,也会导致数组属性无法被正确处理。

修复方案

方案1:调整Flatten类型的实现逻辑

修改Flatten类型,直接遍历所有键生成扁平化片段后合并,避免联合类型分发带来的键推导问题:

export declare type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
declare type StringKeysOf<T> = Extract<keyof T, string>;
declare type PrefixedRecord<Prefix extends string, Obj extends Record<string, any>> = {
    [Key in StringKeysOf<Obj> as `${Prefix}.${Key}`]: Obj[Key];
};
// 修正NonObjectKeysOf:将数组归为对象键范畴
declare type NonObjectKeysOf<T> = {
    [K in keyof T]: T[K] extends Array<any> ? never : T[K] extends object ? never : K;
}[keyof T];
declare type ObjectKeysOf<Obj> = Exclude<keyof Obj, NonObjectKeysOf<Obj>>;

// 修改后的Flatten类型
export declare type Flatten<T> = UnionToIntersection<
    { [K in StringKeysOf<T>]: 
        K extends ObjectKeysOf<T> ? PrefixedRecord<K, T[K]> : { [k in K]: T[K] }
    }[StringKeysOf<T>]
>;

方案2:递归扁平化实现(更简洁)

如果需要支持多层嵌套对象和数组的扁平化,可以使用递归实现,避免依赖UnionToIntersection:

declare type StringKeysOf<T> = Extract<keyof T, string>;

export type Flatten<T> = {
    [K in StringKeysOf<T> as T[K] extends Array<any> | object ? `${K}.${StringKeysOf<T[K]>}` : K]: 
        T[K] extends Array<any> | object ? Flatten<T[K]> : T[K]
};

验证示例

以测试类型为例:

type TestType = {
    name: string;
    profile: {
        age: number;
        isActive: boolean;
    };
    tags: string[];
};

type FlattenedTest = Flatten<TestType>;
// 可正常索引:FlattenedTest["profile.age"] 类型为number,FlattenedTest["tags.0"]类型为string

修改后的代码在TypeScript 4.7.4及以上版本中,可正常通过带前缀的键进行索引访问。

内容的提问来源于stack exchange,提问作者Nainpo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 04:55:17