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

TypeScript中TranslatedObject如何深度转换可选嵌套属性?

修复TypeScript TranslatedObject处理可选嵌套属性的问题

原类型功能

TranslatedObject类型用于TypeScript对象转换,通过条件类型推断和模板字面量类型移除带De、Fr、It、En语言后缀的属性,返回无后缀属性,并递归处理除IgnoredTypes外的对象类型。原定义如下:

/**
 * TranslatedObject works with conditional type inference and template literal types
 * It will remove properties with the language suffixes (De, Fr, It and En) and simply return the non-suffixed property
 * This is done recursively for all properties whose types extend from `object` excluding {@link IgnoredTypes}
 *
 * @example
 * 
 * interface Car {
 *   nameDe: string,
 *   nameFr: string,
 *   nameIt: string,
 *   nameEn: string,
 *   price: number
 * }
 * type TranslatedCar = TranslatedObject<Car>;
 * 
 * Result:
 * type TranslatedCar = {
 *   name: string;
 *   price: number;
 * }
 */
export type TranslatedObject<T> = T extends readonly any[] ? { [I in keyof T]: TranslatedObject<T[I]> } : {
  [K in keyof T as K extends `${infer Name}${'De' | 'Fr' | 'It' | 'En'}` ? Name : K]: T[K] extends object ? (T[K] extends IgnoredTypes[number] ? T[K] : TranslatedObject<T[K]>) : T[K];
};

// IgnoredTypes is needed because `Date` for example extends `object` but Date won't have any translated properties
type IgnoredTypes = [Date];

问题描述

当处理包含可选嵌套属性的对象时,类型转换失效。例如定义包含可选gimmicks属性的Car接口:

interface Car {
  nameDe: string,
  nameFr: string,
  nameIt: string,
  nameEn: string,
  price: number,
  gimmicks?: Gimmick[]
}

type Gimmick = {
 nameDe: string,
 nameEn: string
}

type TranslatedCar = TranslatedObject<Car>;

转换后的TranslatedCar类型中,gimmicks的嵌套类型未被正确转换,赋值时会报错:

Object literal may only specify known properties, and 'name' does not exist in type 'Gimmick'.(2353)

修复方案

修改TranslatedObject类型,增加对undefined类型的处理,并确保联合类型(如Gimmick[] | undefined)能被正确递归转换:

type IgnoredTypes = [Date];

export type TranslatedObject<T> = 
  // 处理数组类型,递归转换每个元素
  T extends readonly any[] ? { [I in keyof T]: TranslatedObject<T[I]> } :
  // 处理对象类型(排除忽略类型)
  T extends object ? (
    T extends IgnoredTypes[number] ? T :
    {
      // 保留原属性的可选性,同时处理语言后缀
      [K in keyof T as K extends `${infer Name}${'De' | 'Fr' | 'It' | 'En'}` ? Name : K]: 
        // 分发联合类型,处理undefined情况
        T[K] extends infer U ? 
          U extends undefined ? U :
          U extends object ? (U extends IgnoredTypes[number] ? U : TranslatedObject<U>) :
          U
        : never;
    }
  ) : 
  // 非对象类型直接返回
  T;

修复说明

  1. 处理联合类型分发:通过T[K] extends infer U触发条件类型的分布式特性,确保undefined和实际对象类型被分开处理;
  2. 保留可选属性的undefined状态:当属性是可选类型时,明确保留undefined分支,避免丢失可选性;
  3. 递归转换嵌套类型:对于非undefined的对象类型,继续递归应用TranslatedObject转换,确保嵌套的带后缀属性也被处理。

验证结果

使用修改后的类型,TranslatedCar会被正确推断为:

type TranslatedCar = {
  name: string;
  price: number;
  gimmicks?: { name: string }[] | undefined;
}

此时赋值操作不再报错:

const car: TranslatedCar = {
  name: 'carname',
  price: 15,
  gimmicks: [{name: 'gimmick 1'}]
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 11:22:05