如何修正TypeScript中JSON深层值替换函数的Output类型?
问题描述
我编写了一个函数,输入为JSON对象和定义替换规则的Map,返回替换所有指定值后的JSON对象(替换值可为任意类型)。但该函数会改变对象类型,我无法在TypeScript中正确反映这一类型变化。以下是函数代码示例:
function replaceJsonValues<Source, Replacement, Output>( obj: Source, translatedKeyData: Map<string, Replacement> ): Output { let stringifiedObject = JSON.stringify(obj); for (const [key, keyProp] of translatedKeyData.entries()) { stringifiedObject = stringifiedObject.replaceAll(`"${key}"`, JSON.stringify(keyProp)); } return JSON.parse(stringifiedObject); } type SourceType = { foo: string; baz: { test: string; } }[] type ReplacementType = { fancy: string; } const source: SourceType = [{ foo: "bar", baz: { test: "bar" } }]; const replacement: ReplacementType = { fancy: "replacement" }; const result = replaceJsonValues(source, new Map([["bar", replacement]])); // ^? 目前TypeScript无法正确推断result的实际类型
解决方案
要让TypeScript自动推导替换后的正确类型,我们需要用递归条件类型描述“将源类型中所有指定字符串值替换为目标类型”的逻辑,不再需要手动指定Output泛型参数。
步骤1:定义递归替换类型
创建一个递归类型,遍历对象所有层级,将匹配指定字符串的类型替换为目标类型:
type ReplaceValues<T, Target extends string, Replacement> = T extends Target ? Replacement : T extends Array<infer U> ? ReplaceValues<U, Target, Replacement>[] : T extends object ? { [K in keyof T]: ReplaceValues<T[K], Target, Replacement> } : T;
这个类型的逻辑:
- 如果当前类型
T等于目标字符串Target,直接替换为Replacement类型; - 如果
T是数组,递归处理数组的每一项; - 如果
T是对象,递归处理对象的每个属性; - 其他类型保持不变。
步骤2:修改函数泛型定义
调整函数的泛型参数,让TypeScript自动用递归类型推导返回值类型:
function replaceJsonValues<Source, Target extends string, Replacement>( obj: Source, translatedKeyData: Map<Target, Replacement> ): ReplaceValues<Source, Target, Replacement> { let stringifiedObject = JSON.stringify(obj); for (const [key, keyProp] of translatedKeyData.entries()) { stringifiedObject = stringifiedObject.replaceAll(`"${key}"`, JSON.stringify(keyProp)); } return JSON.parse(stringifiedObject) as ReplaceValues<Source, Target, Replacement>; }
关键修改点:
- 将
translatedKeyData的键类型限制为Target extends string,确保替换的是明确的字符串值; - 返回类型直接指定为
ReplaceValues<Source, Target, Replacement>,并给JSON.parse添加类型断言(TypeScript无法自动解析动态修改后的JSON结构)。
测试修改后的代码
现在TypeScript会正确推断result的类型:
type SourceType = { foo: string; baz: { test: string; } }[] type ReplacementType = { fancy: string; } const source: SourceType = [{ foo: "bar", baz: { test: "bar" } }]; const replacement: ReplacementType = { fancy: "replacement" }; const result = replaceJsonValues(source, new Map([["bar", replacement]])); // ^? 自动推断为:{ foo: { fancy: string }; baz: { test: { fancy: string } }; }[]
注意事项
- 原函数通过JSON序列化替换字符串的方式,可能存在边缘情况(比如目标字符串出现在属性名或带转义引号的值中),如果需要更严谨的处理,可以改为递归遍历对象的方式实现;
- 递归条件类型在处理深层嵌套对象时表现良好,但过于复杂的源类型可能触发TypeScript的递归深度限制。
内容的提问来源于stack exchange,提问作者fusio
相关产品推荐
相关产品推荐

