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
相关产品推荐
相关产品推荐

