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

如何修正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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 18:15:42