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

如何用TypeScript定义泛型条件映射类型实现对象叶节点转换?

TypeScript 深度对象叶子节点转换实现

需求说明

需要编写TypeScript类型与函数,实现以下功能:

  • 输入:任意深度的对象,其叶子节点结构为 { text: string, [key: string]: string }
  • 输出:结构完全一致,但叶子节点替换为普通string类型的对象(取叶子节点的text属性值)
  • 要求返回类型必须保留对象的具体键,支持通过键索引访问,最终用于生成点标记形式的键(如getTranslation('button.continue'))

示例

输入:

const input = {
  button: {
    continue: {
      text: "Continue",
      isTranslated: false
    },
    next: {
      text: "Proceed",
      context: "Button for next screen control"
    }
  },
  error: {
    text: "Unknown error",
    context: "Err message when specific error cause is not known"
  }
};

输出:

const output = {
  button: {
    continue: "Continue",
    next: "Proceed"
  },
  error: "Unknown error"
}

问题现状

自行编写的代码逻辑在JavaScript层面运行正常,但返回类型被推断为{ [key: string]: never },无法保留具体键的类型信息,需要修正类型定义以实现类型安全。

尝试的代码

type Graphlike<Leaf> = {
  [key: string]: Graphlike<Leaf> | Leaf;
};

type Flattened<T, Leaf, FlattenedValue> = {
  [K in keyof T]: T[K] extends Leaf ? FlattenedValue : T[K] extends T ? Flattened<T, Leaf, FlattenedValue> : never;
};


type TranslationLeaf = {
  text: string;
  [key: string]: unknown;
};

type TranslationMap = Graphlike<TranslationLeaf>;

type FlattenedTranslationMap = Flattened<TranslationMap, TranslationLeaf, string>;

function isTranslationLeaf(obj: TranslationLeaf | TranslationMap): obj is TranslationLeaf {
  return "text" in obj;
}

export function simplifyTranslations<T extends TranslationMap>(obj: T): FlattenedTranslationMap {
  const result = {} as FlattenedTranslationMap;

  for (const key in obj) {
    if (isTranslationLeaf(obj[key])) {
      const node = obj[key] as TranslationLeaf;
      // @ts-ignore
      result[key] = node.text;
    } else {
      const node = obj[key] as TranslationMap;
      // @ts-ignore
      result[key] = simplifyTranslations(node);
    }
  }

  return result;
}

const output = simplifyTranslations(input);

type ResultantType = typeof output;

解决方案

问题出在Flattened类型的递归逻辑上,原类型错误地使用了整个T而非当前键对应的T[K]进行递归。重新定义递归类型即可解决:

修正后的类型定义

// 定义叶子节点类型
type TranslationLeaf = {
  text: string;
  [key: string]: string | boolean; // 匹配示例属性类型,也可使用unknown
};

// 递归定义转换后的对象类型
type SimplifiedTranslationMap<T> = {
  [K in keyof T]: T[K] extends TranslationLeaf 
    ? string 
    : T[K] extends Record<string, any> 
      ? SimplifiedTranslationMap<T[K]> 
      : never;
};

// 输入对象的约束类型
type TranslationMap = {
  [key: string]: TranslationMap | TranslationLeaf;
};

修正后的函数实现

function isTranslationLeaf(obj: unknown): obj is TranslationLeaf {
  return typeof obj === 'object' && obj !== null && 'text' in obj && typeof (obj as TranslationLeaf).text === 'string';
}

export function simplifyTranslations<T extends TranslationMap>(obj: T): SimplifiedTranslationMap<T> {
  const result = {} as SimplifiedTranslationMap<T>;

  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      const value = obj[key];
      if (isTranslationLeaf(value)) {
        result[key] = value.text as SimplifiedTranslationMap<T>[Extract<keyof T, string>];
      } else {
        result[key] = simplifyTranslations(value) as SimplifiedTranslationMap<T>[Extract<keyof T, string>];
      }
    }
  }

  return result;
}

验证效果

const input = {
  button: {
    continue: {
      text: "Continue",
      isTranslated: false
    },
    next: {
      text: "Proceed",
      context: "Button for next screen control"
    }
  },
  error: {
    text: "Unknown error",
    context: "Err message when specific error cause is not known"
  }
};

const output = simplifyTranslations(input);
// typeof output 会正确推断为:
// {
//   button: {
//     continue: string;
//     next: string;
//   };
//   error: string;
// }

// 支持键索引访问,无类型报错
output.button.continue;

关键说明

  • SimplifiedTranslationMap通过递归遍历每个键的类型:如果是TranslationLeaf则转为string,如果是对象则继续递归转换
  • 类型守卫isTranslationLeaf做了更严格的校验,避免误判
  • 函数中使用Extract<keyof T, string>确保键的类型正确映射,移除了@ts-ignore注释

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 02:07:43