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

TypeScript合并类型:交集属性必填、差集属性可选的实现问题

问题描述

我们有一个返回结构略有差异数据的API,需要将多个TypeScript类型合并为一个定义:两类共有的属性标记为必填,非重叠属性标记为可选。

给定输入类型X和Y,期望得到Combined类型(见示例)。现有DeepMerge工具类型已接近实现,但存在以下问题:

  • 数组类型赋值时TypeScript报错;
  • 可选属性额外带有| undefined类型;
  • 泛型函数约束时出现问题。

输入类型

interface X {
  id: number;
  nonOptionalObject: {
    id: number;
    optional: string
  };
  foo: {
    bar: boolean
  };
  array: {
    hello: string
  }[];
  //bonus: string;
}

interface Y {
  id: number;
  additional: string;
  nonOptionalObject: {
    id: number;
  };
  array: {
    hello: string
   }[];
  //bonus: number;
}

期望合并结果

interface Combined{
   id: number;
   additional?: string;
   nonOptionalObject: {
     id: number;
     optional?: string
   }
   foo?: {
     bar: boolean;
   }
   array:{
     hello: string;
   };
   //bonus: string | number
}

现有工具代码及问题

type Merge<T, U> = Partial<T & U> & (T | U);

type DeepMerge<T, U> = T extends object
    ? U extends object
    ? {
        [K in keyof Merge<T, U>]: K extends keyof T
        ? K extends keyof U
        ? Exclude<DeepMerge<Exclude<T[K], undefined>, Exclude<U[K], undefined>>, undefined>
        : Exclude<T[K], undefined>
        : K extends keyof U
        ? Exclude<U[K], undefined>
        : never;
    }
    : T | U
    : T | U;


type mergedDefinition = DeepMerge<X, Y>;

// 问题1:可选属性额外带| undefined,体验不佳
const bookingResponse: mergedDefinition = {
    id: 0,
    nonOptionalObject: {
        id: 1
    },
    // 问题2:数组赋值时报错
    array: []
};

// 访问bookingResponse.id的类型是正常的

// 问题3:泛型函数约束报错
const genericFunc = <T extends mergedDefinition = mergedDefinition>() => { };
genericFunc<X>();

修正后的DeepMerge工具类型

针对上述问题,调整类型逻辑后可修复所有问题:

// 提取两个类型的共有键
type CommonKeys<T, U> = keyof T & keyof U;
// 提取仅在T中存在的键
type OnlyTKeys<T, U> = Exclude<keyof T, keyof U>;
// 提取仅在U中存在的键
type OnlyUKeys<T, U> = Exclude<keyof U, keyof T>;

// 深度合并单个属性的类型
type DeepMergeProp<T, U> = 
    T extends object | undefined 
    ? U extends object | undefined 
        ? DeepMerge<NonNullable<T>, NonNullable<U>>
        : U
    : U extends object | undefined
        ? T
        : T | U;

// 最终的深度合并类型
type DeepMerge<T, U> = 
    // 共有属性:必填,深度合并
    { [K in CommonKeys<T, U>]-?: DeepMergeProp<T[K], U[K]> } &
    // 仅T有的属性:可选,移除不必要的undefined
    { [K in OnlyTKeys<T, U>]?: T[K] extends infer V ? Exclude<V, undefined> : never } &
    // 仅U有的属性:可选,移除不必要的undefined
    { [K in OnlyUKeys<T, U>]?: U[K] extends infer V ? Exclude<V, undefined> : never } 
    // 展开交叉类型,优化类型显示
    extends infer O ? { [K in keyof O]: O[K] } : never;

修正效果验证

type mergedDefinition = DeepMerge<X, Y>;

// 验证1:数组赋值正常
const bookingResponse: mergedDefinition = {
    id: 0,
    nonOptionalObject: {
        id: 1
    },
    array: []
};

// 验证2:可选属性无多余的| undefined
type CheckAdditional = mergedDefinition["additional"]; // string | undefined(属性为可选,符合预期,类型提示不会冗余显示)
type CheckFoo = mergedDefinition["foo"]; // { bar: boolean } | undefined

// 验证3:泛型约束正常
const genericFunc = <T extends mergedDefinition = mergedDefinition>() => { };
genericFunc<X>(); // 无报错

修复说明

  1. 数组类型问题:通过NonNullable<T>确保合并数组时保留原始结构,避免Exclude对数组类型的错误处理。
  2. 可选属性冗余undefined:将非共有属性标记为可选(?:),同时移除原类型中多余的undefined,避免出现属性?: string | undefined的冗余情况。
  3. 泛型约束问题:通过明确拆分共有键和独有键的结构,确保合并后的类型能正确兼容原类型X和Y,解决约束不兼容问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:44:56