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

如何递归泛型转换{ a: A | undefined }为{ a?: A }?

解决方案:递归转换可选属性类型并清理undefined字段

类型层面:定义递归泛型转换类型

首先创建一个泛型类型UndefinedToOptional,它会递归遍历目标类型的所有字段,将类型为X | undefined的属性转换为可选属性?: X,同时处理嵌套的对象和数组:

type UndefinedToOptional<T> = 
  // 处理null/undefined原始类型
  T extends null | undefined ? T :
  // 处理数组,递归转换数组元素类型
  T extends Array<infer U> ? Array<UndefinedToOptional<U>> :
  // 处理对象类型
  T extends object ? {
    [K in keyof T as T[K] extends undefined ? never : K]?: UndefinedToOptional<Exclude<T[K], undefined>>
  } : 
  // 基础类型直接返回
  T;

运行时:递归清理undefined属性

由于开启exactOptionalPropertyTypes: true后,{ a: undefined }和{ }结构不等价,且schema库会做运行时验证,必须手动移除所有值为undefined的属性。以下是对应的递归清理函数:

function cleanUndefinedProperties<T>(obj: T): UndefinedToOptional<T> {
  // 处理null/undefined
  if (obj === null || obj === undefined) return obj as UndefinedToOptional<T>;
  // 处理数组,递归清理每个元素
  if (Array.isArray(obj)) {
    return obj.map(item => cleanUndefinedProperties(item)) as UndefinedToOptional<T>;
  }
  // 处理对象,遍历并清理每个属性
  if (typeof obj === 'object') {
    const cleaned = {} as UndefinedToOptional<T>;
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        const value = obj[key];
        const cleanedValue = cleanUndefinedProperties(value);
        // 仅保留非undefined的属性值
        if (cleanedValue !== undefined) {
          (cleaned as Record<string, unknown>)[key] = cleanedValue;
        }
      }
    }
    return cleaned;
  }
  // 基础类型直接返回
  return obj as UndefinedToOptional<T>;
}

使用示例

假设你有如下源类型和对象:

// 源类型:部分字段为X | undefined
type SourceData = {
  name: string | undefined;
  profile: {
    age: number | undefined;
    tags: Array<{ label: string | undefined }>;
  };
};

// 转换后的目标类型:对应字段变为可选属性
type TargetData = UndefinedToOptional<SourceData>;
// TargetData 等价于:
// {
//   name?: string;
//   profile?: {
//     age?: number;
//     tags?: Array<{ label?: string }>;
//   };
// }

处理源对象:

const rawData: SourceData = {
  name: undefined,
  profile: {
    age: undefined,
    tags: [{ label: undefined }, { label: "typescript" }]
  }
};

const processedData = cleanUndefinedProperties(rawData);
// processedData 的实际结构:
// {
//   profile: {
//     tags: [{}, { label: "typescript" }]
//   }
// }
// 既符合TargetData的类型要求,也能通过schema库的运行时验证

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 01:04:59