使用 const 断言,如何从任意嵌套对象中提取文字类型?

2024-03-05

这个问题是后续问题this one https://stackoverflow.com/q/76288737/6923555,其中我有一个深度为 2 的结构:

const grandkids = {
  Karen: {
    Ava: ['Alice', 'Amelia'],
    Emma: ['Sarah'],
  },
  Mary: {
    Sophia: ['Grace'],
  },
} as const;

对于上面的示例,我可以使用以下方法提取文字类型:

type Grandmother = keyof typeof grandkids; // Karen|Mary

type Mother = {
  [K in Grandmother]: keyof typeof grandkids[K];
}[Grandmother]; // Ava|Emma|Sophia

type Kid = {
  [K in Grandmother]: {
    [M in Mother]: typeof grandkids[K][M extends keyof typeof grandkids[K] ? M : never];
  };
}[Grandmother][Mother][number]; // Alice|Amelia|Sarah|Grace

但是如果我有一个 3 层结构怎么办?

const greatgrandkids = {
  Beth: {
    Karen: {
      Ava: ['Alice', 'Amelia'],
      Emma: ['Sarah'],
    },
    Mary: {
      Sophia: ['Grace'],
    },
  },
  Ruth: {
    Ella: {
      Scarlett: ['Charlotte'],
    },
  },
};

对于上面的结构,也有一个祖祖母,我们有一个附加类型,这是第一层:

type GreatGrandmother = /* CODE?? */; // Beth|Ruth
type Grandmother = /* ... */; // Karen|Mary|Ella
type Mother = // ...

对于任意数量的级别,该示例仍在继续。考虑均匀的深度,完整的高度直至叶田。


对于该结构的大部分,您正在寻找union https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types字符串的文字类型 https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html对应于某个固定深度的键。所以我们可以写一个实用程序类型KeysAtDepth<T, N>所以说,KeysAtDepth<T, 0>对应的键T直接,并且KeysAtDepth<T, 1>对应于所有属性的键T, and KeysAtDepth<T, 2>对应所有子属性的键T, etc.

这是一种写法:

type KeysAtDepth<T, N extends number, A extends any[] = []> =
  T extends unknown ? (
    N extends A['length'] ? keyof T : KeysAtDepth<T[keyof T], N, [0, ...A]>
  ) : never;

这是一个递归条件类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#recursive-conditional-types。 TypeScript 无法真正进行数学运算number直接输入(参见微软/TypeScript#26382 https://github.com/microsoft/TypeScript/issues/26382), 但它can表示追加到的操作元组类型 https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types (via 可变元组类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#variadic-tuple-types)并检查他们的length属性,所以我们可以编码“递归直到深度N" as "在元组前面添加一个元素A每次我们递归然后停止时A['length'] is N".

递归的基本情况是返回keyof T;否则我们申请KeysAtDepth to T[keyof T],意味着所有属性的并集T.

这解释了那里发生的大部分事情;剩下的唯一部分是解释为什么我们有T extends unknown ? ⋯ : never,看起来它没有做任何事情。原因是为了让KeysAtDepth<T, N> a 分配条件类型 https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types in T, 以便KeysAtDepth<A | B, N>相当于KeysAtDepth<A, N> | KeysAtDepth<B, N>。如果没有它,你最终会得到类似的东西keyof (A | B)这不是你想要的(它只会显示存在于all工会成员)。


让我们测试一下:

const greatgrandkids = {
  Beth: {
    Karen: {
      Ava: ['Alice', 'Amelia'],
      Emma: ['Sarah'],
    },
    Mary: {
      Sophia: ['Grace'],
    },
  },
  Ruth: {
    Ella: {
      Scarlett: ['Charlotte'],
    },
  },
} as const;

type GreatGrandKids = typeof greatgrandkids;
type GreatGrandMother = KeysAtDepth<GreatGrandKids, 0>
// type GreatGrandMother = "Beth" | "Ruth"
type GrandMother = KeysAtDepth<GreatGrandKids, 1>
// type GrandMother = "Karen" | "Mary" | "Ella"
type Mother = KeysAtDepth<GreatGrandKids, 2>;
// type Mother = "Ava" | "Emma" | "Sophia" | "Scarlett"

到目前为止,一切都很好。


理想情况下你的greatgrandkids结构将是统一的,并且自始至终都使用按键,例如

const greatgrandkids = {
  Beth: {
    Karen: {
      Ava: { Alice: {}, Amelia: {} },
      Emma: { Sarah: {} }
    },
    Mary: {
      Sophia: { Grace: {} }
    },
  },
  Ruth: {
    Ella: {
      Scarlett: { Charlotte: {} }
    },
  },
} as const;

然后最终的类型就是深度的键3:

type Child = KeysAtDepth<GreatGrandKids, 3>
// type Child = "Alice" | "Amelia" | "Sarah" | "Grace" | "Charlotte"

但事实并非如此,所以我们需要另一种实用程序类型,例如TypesAtDepth<T, N>抓住联盟types在特定的深度。所以TypesAtDepth<T, 0>将会T, and TypesAtDepth<T, 1>将会T[keyof T](深度 1 处类型的并集)等

几乎是一样的:唯一的区别是基本情况是T代替keyof T:

type TypesAtDepth<T, N extends number, A extends any[] = []> = 
  T extends unknown ? (
    N extends A['length'] ? T : TypesAtDepth<T[keyof T], N, [0, ...A]>
  ) : never;

And now TypesAtDepth<GreatGrandKids, 3>是一个联盟只读元组类型 https://www.typescriptlang.org/docs/handbook/2/objects.html#readonly-tuple-types包含字符串文字,我们可以index https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html with number获取元素类型的并集:

type Child = TypesAtDepth<GreatGrandKids, 3>[number]
// type Child = "Alice" | "Amelia" | "Sarah" | "Grace" | "Charlotte"

Playground 代码链接 https://www.typescriptlang.org/play?#code/LAKFBcE8AcFMAIDStIGcCC4AitrgBYA8AKgDTwBy8sAHuLAHYAmq8DArgLYBGsATuXTU6jFvACGDSAG0AuvAC88OQD5FoePGLD6zVuwYBrBgHsA7g3gB+eAAoNmyjtGt00gOQAbRgHMC7+RtDFBMAMy14AC4kFAxsXAISaWDIMK1ZcgpyaQAGcgA6QvRZFQcASii2WAA3fgBuUEaQAGMTBlRweB8+WHFwbskmQwBLMSUAbwcAIVgCaMmQRyRxHoZ5hyX0avFoj3RPYebYd3J3dE5YA-EA0g3HAFFOTh3ldwBlFfF8G7uAX1vFo4ALIrSDrQFLN4maD4YYvDwAcT44iOPwh8H+DkxgIASuw5vAFkt7p5PC8iUt4G9mitvOBwLt3ABhfC0kz044ZP4AzTY34SVitdrgBogKBweBI3rgJGDRCjVhKcWwNLdaUDZgjFii5WSnp9WXMIHs-D8RQxNCYHB4IhSg3I5jyljkHKlEAAend8F1dplDqYxoIZqUACIZgQQ-AAD7wEN4iMQGAIQ0Bk3Bi1xa2JX0pp2ocgARjdnu9Sb1g0Dpr45pDiBWjEjMZDIL4kEbsZJZJDiYllfTyEt8RthBz-rz5AATCpRSXdX3q6GtuJ2yHHs8V1CYXCNzS+HTwN2wGKy8Qk5mEkQyE5aLoxBwePxBM49BIpHJzapzdoby54AZjOYlhWA4VA-i+bheL4-iBBE0SnnA57DsQyQhOExAZJQ2R5PAhT5MUajRAwNT1E0uossMnhMF+Z5WheI76n6coKuQADMKjSPevB8LIoCzmW5GUTW+yHLAK7nJc27RrGHzIvgK6ykcK4smyHKHqADDiBcqDQCiCAAKoMMMoQmHwnBvOAfDsM04DsD0hIOEKHRdAxGpDAq5oUpo4b4OClLLKsvl+fAS6BUFwUHEc8zYmFmjiVcUV3Es0VBWu5KJZSMlfAl6JJelyVLC2YL2TljibrCaUlUsCmwNlYW-HlPKOPl8Dxj5xV+Z2FVhdStKzAy7Uxcpe7svQtVBfVJXNcl-LiIKbQdKKDi6gOiGJFeoEiC+nGPsFz5iJIMjyEon5KN+m1iP+pgWNYdwbbergeN4DB+N8MEpGk2jRCttFIShqRoRhWTKNhuH4ZURG1Hwi2Aj6DG5u5SpJqqLn+lqqCipoZGwoJSjfUO2Zw2OzHwGxDh8RKAlUYuEWiVJIZxZJTaZXJdPVUprLDapTS-EAA

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 const 断言,如何从任意嵌套对象中提取文字类型? 的相关文章

随机推荐

  • 将 csv 中的列转换为日期 powershell

    我有一个与此类似的 csv fundName MMFcusip ticker AsOfDate SumHoldingPercent BlackRock OH Muni MMP Instit 091927236 COIXX 2 29 2012
  • 在 LISP 中如何检查闭包中的自由变量?

    在 lisp 中 我可以像这样绑定在闭包中绑定的自由变量 let x 1 y 2 z 3 defun free variables x y z free variables 结果是 6 我想知道是否可以动态检查绑定的闭包变量 E g ins
  • 嵌入 python 错误 不支持按文件名导入

    我正在尝试将 python 嵌入到我的应用程序中 但很早就陷入了困境 我将 python 嵌入到我的 C 应用程序中并使用本教程中找到的代码 http docs python org 2 extending embedding html p
  • Android:使用 DrawableCompat 着色

    我正在尝试对 Android API 级别 21 之前的图像进行着色 我已经使用以下方法成功对项目进行了着色
  • XHR跨域限制的目的是什么?

    我一直想知道XHR跨域限制的目的是什么 其目的似乎是防止恶意注入的 Javascript 将私有数据发送给攻击者 然而 通过注入可以轻松地将数据发送到任何域script or img标签 或任何其他与此相关的外部资源 如果任意网站可以对您的
  • django Rest框架::传递原始查询

    是否可以在 django Rest 框架 如 django rest 中执行原始查询 https docs djangoproject com en dev topics db sql performing raw queries http
  • 在平板电脑模式下在最上面启动另一个应用程序

    当我从应用程序运行另一个 exe 时 它 在后台启动 并且不会在屏幕顶部显示该应用程序 而是显示平板电脑模式主屏幕 它在正常桌面模式下工作正常 但当我在 Windows 10 平板电脑模式下运行它时它不会显示在顶部 而是在后台启动 我用过m
  • 什么是UUID?

    嗯 什么是一 它是唯一标识某物的标识号 这个想法是 id 号码将是普遍地独特的 因此 任何两个事物都不应该具有相同的 uuid 事实上 如果您要生成 10 万亿个 uuid 则两个 uuid 相同的概率为 0 00000006
  • 模块自动加载就意味着可靠吗?

    环境 我有以下文件夹结构 用于保存 powershell 模块 C PsModules util util psm1 this contains implementation of Test Function util test ps1 f
  • 将枚举值映射到 TypeScript 中的各个类型

    我有一段 TypeScript 代码 如下所示 enum EventType EVENT A eventA EVENT B eventB more event types interface Event type EventType int
  • 在 Android 上开发电子邮件客户端应用程序

    我正在尝试开发一个用于在Android平台上发送和接收电子邮件的小应用程序 目前我一直在使用 Javamail api 尝试发送电子邮件 但是我想 如果我使用 javamail 实现我的应用程序 我将如何接收电子邮件并从我的应用程序收到我已
  • 一个 python 脚本可以同时运行 python 2.x 和 python 3.x

    我有数千台服务器 linux 有些只有 python 2 x 有些只有 python 3 x 我想编写一个脚本 check py 可以在所有服务器上运行 就像 check py 一样 无需使用 python check py 或 pytho
  • 如何将 Azure Active Directory 身份验证添加到 Razor Pages 应用程序?

    据我了解 您可以通过执行 新建项目 gt ASP NET Core Web 应用程序 gt 提供应用程序名称 gt Web 应用程序 在 Visual Studio 2019 中创建 Razor Pages 应用程序 以下教程演示如何将 A
  • 删除整数中的重复数字[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我在技术回合中遇到过这个程序 他们给了我这个程序来删除给定整数中的重复数字不使用数组或字符串 Example int i 12313425
  • jquery change() 警报一键触发 6 次?

    jQuery document ready function c5sliderSelect change function alert change clicked 当我更改选择列表项时 我收到 6 条警报 不知道为什么 我的标记中只有一个
  • JTable 中特定列的比较器

    如何为 JTable 中的特定列设置自定义比较器 我的表的第三列包含双精度值的字符串表示形式 我想为该列创建一个比较器 以便当我单击该列的标题时 它将根据该比较器进行排序 第一个问题是 如果您正在管理双打 为什么您正在处理字符串 如果您使用
  • 防止 Backbone.js 模型在首次添加到集合时进行验证

    首次创建新模型时 有没有办法在 Backbone js 中抑制模型验证 在我的应用程序中 我有一个包含任意数量模型的集合 这些模型表示为列表项 用户可以单击每个项目上的按钮 这会在当前项目下方插入一个新的空项目 显然 空项目未通过验证 因为
  • 如何将 IPV6 地址转换为 IPV4 地址?

    我有使用 IPv4 地址的应用程序 它存储它们很长 因此它只理解 IPv4 地址 是否可以使用Java将IPv6地址转换为IPv4地址 While IPv4 地址范围有 IPv6 等效项 https en wikipedia org wik
  • archiveBaseName 应用于所有构建类型

    我有以下应用程序build gradle android compileSdkVersion 23 buildToolsVersion 23 0 1 defaultConfig applicationId io gresse hugo an
  • 使用 const 断言,如何从任意嵌套对象中提取文字类型?

    这个问题是后续问题this one https stackoverflow com q 76288737 6923555 其中我有一个深度为 2 的结构 const grandkids Karen Ava Alice Amelia Emma