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

TypeScript中if分支内泛型未正确推导类型问题求助

解决TypeScript递归Map转对象函数的泛型推导问题

问题根源

当通过itemToConvert instanceof Map判断输入为Map类型时,TypeScript无法自动将泛型参数T缩小为仅Map类型,导致acc被推断为object | unknown[] | Record<string | number | symbol, unknown>联合类型,进而触发索引类型错误。我们需要手动缩小类型范围,让TS明确此时的返回值应为Record类型。

修改后的完整代码

type InputMapToObject =
    | Map<keyof any, unknown>
    | Record<keyof any, unknown>
    | unknown[];

type ReturnMapToObject<T extends InputMapToObject> = T extends Map<
    infer KMap extends keyof any,
    infer VMap
>
    ? Record<
          KMap,
          VMap extends InputMapToObject ? ReturnMapToObject<VMap> : VMap
      >
    : T extends object
    ? {
          [P in keyof T]: T[P] extends InputMapToObject
              ? ReturnMapToObject<T[P]>
              : T[P];
      }
    : T extends (infer VArray)[]
    ? VArray extends InputMapToObject
        ? ReturnMapToObject<VArray>[]
        : VArray[]
    : never;

export function mapToObject<T extends InputMapToObject>(
    itemToConvert: T
): ReturnMapToObject<T> {
    // 处理Map类型:手动缩小类型范围,明确acc为Record类型
    if (itemToConvert instanceof Map) {
        const map = itemToConvert as Map<keyof any, unknown>;
        return [...map.entries()].reduce((acc, [key, value]) => {
            acc[key] = isRecursiveValue(value) ? mapToObject(value) : value;
            return acc;
        }, {} as ReturnMapToObject<typeof map>) as ReturnMapToObject<T>;
    }

    // 处理数组类型:断言map结果匹配返回类型
    if (Array.isArray(itemToConvert)) {
        return itemToConvert.map((item) => 
            isRecursiveValue(item) ? mapToObject(item) : item
        ) as ReturnMapToObject<T>;
    }

    // 处理普通对象类型
    return getObjectEntries(itemToConvert).reduce((acc, [key, value]) => {
        acc[key] = isRecursiveValue(value) ? mapToObject(value) : value;
        return acc;
    }, {} as ReturnMapToObject<T>);
}

// 优化类型守卫,避免any类型
function isRecursiveValue(item: unknown): item is InputMapToObject {
    return item instanceof Map || Array.isArray(item) || (typeof item === 'object' && item !== null);
}

function getObjectEntries<T extends Record<any, any>>(
    value: T
): [keyof any, T[keyof T]][] {
    return Object.entries(value) as [keyof any, T[keyof T]][];
}

关键调整说明

  1. Map分支类型缩小
    将itemToConvert断言为Map<keyof any, unknown>,并将reduce初始值acc断言为ReturnMapToObject<typeof map>。此时TS能识别acc是Record类型(对应ReturnMapToObject的Map分支定义),解决了索引错误。最后将结果断言为ReturnMapToObject<T>,由于此时T必然是Map子类型,该断言安全有效。

  2. 递归处理嵌套值
    在所有分支中补充了对嵌套值的递归转换,确保嵌套的Map、对象或数组也能被正确处理,符合递归函数的设计需求。

  3. 优化类型守卫
    将isRecursiveValue的参数类型从any改为unknown,并简化判断逻辑,提升类型安全性。

测试验证

interface TestingType {
    testMap: Map<string, number>;
    testArray: Array<number>;
    testArray2: Array<Map<string, string>>;
    nested: {
        innerMap: Map<number, boolean>;
    };
}

const input: TestingType = {
    testMap: new Map([['string', 0]]),
    testArray: [0],
    testArray2: [new Map([['string', 'string']])],
    nested: {
        innerMap: new Map([[1, true]])
    }
};

const output = mapToObject(input);
// 输出类型自动推导为:
// {
//     testMap: Record<string, number>;
//     testArray: number[];
//     testArray2: Record<string, string>[];
//     nested: { innerMap: Record<number, boolean> };
// }

内容的提问来源于stack exchange,提问作者Jordi Baliellas Portet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 18:07:06