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

TypeScript如何在类型层面强制嵌套对象键的唯一性

实现嵌套对象全局唯一键的TypeScript类型约束

需求背景

定义对象类型,确保所有嵌套层级的对象间无重复键。例如若foo.bar包含hello键,则foo.baz不可包含该键,需在类型层面实现此约束。

问题拆解

  • 如何实现单层级嵌套的NestedUniqueKeys类型?
  • 如何扩展至任意层级嵌套?
  • 最终实现能否自动推断所有键,无需传入类型参数?

已尝试代码及问题

初始尝试(问题:所有嵌套键被禁用)

type NestedUniqueKeys<Keys extends string = never> = {
  [K in string]: K extends Keys
    ? never
    : string | NestedUniqueKeys<K|Keys>
}

二次尝试(问题:不允许嵌套对象存在任何键)

type NestedUniqueKeys<Keys extends string = never> =
  { [K in string]: K extends Keys ? never : string } extends infer ThisLevel
  ? keyof ThisLevel extends string
  ? ThisLevel & {
    [N in string]: N extends Keys ? never : NestedUniqueKeys<keyof ThisLevel|Keys>
  }
  : never
  : never

分步解决方案

第一步:实现单层级嵌套的唯一键约束

单层级约束要求当前对象的直接键与子对象的键完全互斥,核心是将当前层级的键传递给子对象作为禁用集合:

// 单层级版本:当前对象键与直接子对象键不可重复
type SingleLevelUniqueKeys<UsedKeys extends string = never> = {
  [K in string]: K extends UsedKeys 
    ? never 
    : string | { [SubK in string]: SubK extends UsedKeys | K ? never : string }
}

测试验证:

// 合法:bar的键hello未在foo层级使用
const valid: SingleLevelUniqueKeys = {
  foo: "abc",
  bar: { hello: "world" }
}

// 非法:bar的键foo与上层键重复,类型报错
const invalid: SingleLevelUniqueKeys = {
  foo: "abc",
  bar: { foo: "world" }
}

第二步:扩展至任意层级嵌套

要支持无限嵌套,需要递归传递已使用的键集合,每进入一层子对象就把当前层级的键加入禁用集合:

// 任意层级版本:递归传递已使用键集合,全局约束唯一
type DeepNestedUniqueKeys<UsedKeys extends string = never> = {
  [K in string]: K extends UsedKeys 
    ? never 
    : string | DeepNestedUniqueKeys<UsedKeys | K>
}

测试验证:

// 合法:所有层级键无重复
const deepValid: DeepNestedUniqueKeys = {
  foo: "abc",
  bar: {
    hello: "def",
    world: {
      test: "xyz"
    }
  }
}

// 非法:world下的bar与上层bar重复,类型报错
const deepInvalid: DeepNestedUniqueKeys = {
  foo: "abc",
  bar: {
    hello: "def",
    world: {
      bar: "xyz" // 此处类型不兼容
    }
  }
}

第三步:自动推断所有键,无需传入类型参数

上面的DeepNestedUniqueKeys已经实现了自动推断——我们给UsedKeys设置了默认值never,使用时直接写DeepNestedUniqueKeys即可,TypeScript会自动递归追踪所有已使用的键。

如果需要事后验证已有对象的键唯一性,可以封装一个工具类型提取所有层级的键并检查:

// 递归提取对象所有层级的键
type ExtractAllKeys<T> = T extends object 
  ? keyof T | { [K in keyof T]: ExtractAllKeys<T[K]> }[keyof T]
  : never

// 验证对象所有层级键是否唯一
type ValidateUniqueNestedKeys<T> = 
  ExtractAllKeys<T> extends infer Keys 
  ? Keys extends string 
    ? (Keys extends any[] ? never : Keys) extends never 
      ? T 
      : never 
    : never
  : never

使用方式:

// 合法:键无重复,类型校验通过
const validObj = {
  foo: "abc",
  bar: { hello: "world" }
} satisfies ValidateUniqueNestedKeys<typeof validObj>

// 非法:存在重复键bar,类型报错
const invalidObj = {
  foo: "abc",
  bar: { bar: "world" }
} satisfies ValidateUniqueNestedKeys<typeof invalidObj>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 02:25:53