分两步使用类型安全属性访问递归解析对象

2023-12-21

我尝试将以下函数中的字符串类型替换为更具体的类型,以确保类型安全的属性访问:

import {get} from 'lodash';

const obj = {
  foo: 'foo',
  bar: {
    a: 'Hello',
    b: {c: 'World'}
  }
};

function factory(namespace?: string) {
  return function getter(key: string) {
    return get(obj, [namespace, key].filter((part) => part != null).join('.'));
  };
}

const getter = factory('bar');
getter('b.c'); // 'World'

点符号表示嵌套的属性访问。它可以同时存在于namespace以及在key.

到目前为止我发现我可以输入namespace使用此实用程序:

type NestedKeyOf<ObjectType extends object> =
  {
    [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
      ? `${Key}` | `${Key}.${NestedKeyOf<ObjectType[Key]>}`
      : `${Key}`
  }[keyof ObjectType & (string | number)];

Usage: namespace?: NestedKeyOf<typeof obj>.

然而,我正在努力想出一个自动分配给的动态类型key.

另外两个要求是:

  1. 命名空间应该只解析为对象(没有叶字符串)。
  2. getter 应始终解析为叶字符串(无对象)。

可以假设对象中只存在对象和字符串,没有其他内容。

// Test cases

// Should pass
factory()('foo')
factory('bar')('a')
factory('bar')('b.c')

// Invalid property access
// @ts-expect-error 
factory('baz')
// @ts-expect-error
factory('bar')('d')

// Only partial namespaces are allowed
// @ts-expect-error 
factory('foo')

// Getter calls need to resolve to a leaf string
// @ts-expect-error
factory('bar')('b')

任何帮助将非常感激!非常感谢!


我不敢相信这个可恶的东西真的有效:

function factory<NestedKey extends NestedKeyOf<typeof obj>>(namespace?: NestedKey) {
  return function getter<
    TargetKey extends 
      (NestedKey extends undefined
        ? NestedKeyOf<typeof obj>
        : NestedKeyOf<Get<typeof obj, NestedKey>>)
  >(key: TargetKey): NestedKey extends undefined ? Get<typeof obj, TargetKey> : Get<typeof obj, `${NestedKey}.${TargetKey}`> {
    return get(obj, [namespace, key].filter((part) => part != null).join('.'));
  };
}

请允许我解释一下。首先,我在执行 Nested KeyOf 时遇到了很多麻烦:(

我必须重写它,因为它报告类型实例化太深,所以这是我的版本:

type NestedKeyOf<O> = O extends object ? {
    [K in keyof O]: `${K & string}` | `${K & string}.${NestedKeyOf<O[K]>}`;
}[keyof O] : never;

它做同样的事情;只是写法不同。接下来我们需要能够“深度获取”一个属性(模仿 lodash 的 get 函数):

type Get<O, P extends string> =
  P extends `${infer Key}.${infer Rest}`
    ? Key extends keyof O ? Get<O[Key], Rest> : never
    : P extends keyof O ? O[P] : never;

额外的extends keyof O是为了防止无效属性访问时出现错误。

最后就是我上面展示的怪物。

我们需要存储什么namespace是,所以我们使用泛型。这个通用名称恰如其分NestedKey我们现在可以在定义中使用它getter.

getter还需要一个嵌套键。然而,它的类型不同时namespace未提供。

因此NestedKey extends undefined有没有。如果它不存在,那么key应该是原始对象的嵌套键。否则,它是值的嵌套键namespace指向,使用Get.

最后在返回值中,我们做了同样的事情。如果NestedKey不存在,则我们深度获取目标键,否则,我们深度获取嵌套键和目标键。

操场 https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbwOYFMYF84DMoRHAcgBsIATAQwGcALAgbgFgAoZmATzBTgDkVKYUpANIo2AeSwAeMQD44AXjhi4KAB4CAdqUpwIAIwBWKAMbwA-ImZxrcANpC4wDXADWoiFiUBdAFxwABgAkCA4AZHD8UE5I6P5wAD4BwWERMFEaMQB0wbz8giLiUmL2XjKxjEzotm5sHt5wfhooAG4oUBWsHFwA4mjSADRwAAoq6ihaOpHRcvJWw6Oa2kkITlhtcAXo2Ssaa1BwAEp8GP5z1hYFC+NLNXXKFr0w0vaiXoNH-HKNLW1nDfNqRY6W6ee5KWxDLz-JqtdrMZjGCAafi6QwKSxMaxYCAQPwEbEQAj9OZ6chQPwIP7kPEACRQRBIRL+egpxjxAHVoERSAR0HM+ZU4FQ4IjkTAOkwsABXDSmYBI7DkUzQNiSXICYSiK4THjHfKiCSSdicOr6AwyGQACg05BAfDASpQZj86v1bAAlBjrFA0FKoM5pbKYPLnKgYAIoJI-gAVMlhy6A646P7WS2uzVsbVLGWkFBYJyCFM2CzpgqG40oU2GGRF6wuvUZw2PI1dKsGQal0QW91zK01PyxqDx0Tu+t5DNZnQ5vMF0hwB59CttwaD4dsL5wZtLzxmwZBBCdthbYKrtCbfxySmYmw+mB+0NoS27uw2u2UB3GFCDGpeTL5ogRpaloOrAnryHIIHwAAhIoGhSgy7qZAYEBOJaBCZAQ7ruhU1joBUAoIkiKJhhG6JYEqMAqpa2HMCRbRoXomTGJhdBwAA9GxhCclA3IEEAA

The as const断言是为了验证它实际上是深度获得正确的值。


根据新要求进行更新。我们需要一些新类型来告诉我们哪些键是对象,哪些是字符串:

type GetObjectKeys<O, K extends string> = {
  [P in K]: Get<O, P> extends string ? never : P;
}[K];

type GetStringKeys<O, K extends string> = {
  [P in K]: Get<O, P> extends string ? P : never;
}[K];

它们采用对象类型和一些潜在的键。它检查每个键的类型。对于对象来说,如果它是一个字符串,那么它是never, 否则P。我们用never因为下面我们将得到所有剩余键的并集[K] and T | never简化为T.

然后因为namespace是可选的,它带来了一些困难。我的一个错误是相信如果namespace没有提供,NestedKey将是未定义的(所以之前的答案实际上是不正确的)。稍后将对此进行更正。

为了解决可选的namespace,我们制作原始的factory函数的参数namespace not可选并将其重命名为_factory(内部/私人)。然后我们创建一个新的factory像这样调用它的函数:

function factory<NestedKey extends GetObjectKeys<typeof obj, NestedKeyOf<typeof obj>>>(namespace?: NestedKey) {
  return _factory<
    { __private: typeof obj },
    GetObjectKeys<typeof obj, NestedKeyOf<typeof obj>> extends NestedKey ? "__private" : `__private.${NestedKey}`
  //@ts-ignore Unfortunately I don't think there is a good way to prevent this error
  >({ __private: obj }, namespace ? `__private.${namespace}` : "__private");
}

它需要任何对象键,但如果未提供名称空间,它将创建默认值__private,因为我们用属性将目标对象包装在另一个对象中__private避免命名空间是可选的这一事实。将其视为委托者。

现在对于修改后的_factory功能:

function _factory<Obj extends unknown, NestedKey extends NestedKeyOf<Obj>>(obj: Obj, namespace: NestedKey) {
  return function getter<
    TargetKey extends GetStringKeys<Get<Obj, NestedKey>, NestedKeyOf<Get<Obj, NestedKey>>>
  >(key: TargetKey): Get<Obj, `${NestedKey}.${TargetKey}`> {
    return get(obj, [namespace, key].filter((part) => part != null).join('.'));
  };
}

该函数与原始函数基本相同,只是现在它只需要生成字符串的键。处理可选的部分namespace已搬入新的factory功能。

我本可以选择更好的名字factory and _factory为了避免混淆,但希望您已经足够详细地跟随我完成了这个过程。

操场 https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbwOYFMYF84DMoRHAcgBsIATAQwGcALAgbgFgAoZmATzBTgDkVKYUpANIo2AeSwAeMQD44AXjhi4KAB4CAdqUpwIAIwBWKAMbwA-ImZxrcANpC4wDXADWoiFiUBdAFxwABgAkCA4AZHD8UE5I6P5wAD4BwWERMFEaMQB0wbz8giLiUmL2XjKxjEzotm5sHt5wfhooAG4oUBWsHFwA4mjSADRwAAoq6ihaOpHRcvJWw6Oa2kkITlhtcAXo2Ssaa1BwAEp8GP5z1hYFC+NLNXXKFr0w0vaiXoNH-HKNLW1nDfNqRY6W6ee5KWxDLz-JqtdrMTqcOCPMSGEwwAqUAYbK4TVLpJAzSxMawQxzOIS+JF9MSDIZyQHXSZpaJwCww9Z+IYVKoUjpMdiIx4AZWZGQxWIcDNxUwyhIQc1JTg2lMeWLpOKWMqQrPm31h3JKfOMEA0-F0hgUROsWAgED8BBtEAI-TmenIUD88uJNnI9oAEigiCRnX89J7jPaAOrQIikAjoOYJypwKhwY2mmB8rAAVw0pmAJrgAH0sORTNA2NILVKlrmXBoIAB3DSDXICYSiDU6Nv5UQSKsGGQyAAU+gMfhRBkGGnIID4YDLKD8PY7bAAlFa4FA0NmoM4c3mYAXnKgYAIoJI-gAVd2ny41nTC0VIcWqyet469tgyD95Vf9t9DF-dsCiHGQ5hHGo-BvKA71ENc-EAqdlhXTZthguC2FiOQvRsLcdz3OBT1HIC7BnOdKAXYwUEGGovEyLBgCIc9h2HBdYA3eQ5HY+AAEJFA0bMgzXTIDAgJxhwITICDXNcKmsdBuXhJgD3zQtS3LKBK1QzsHypGBJzRcUBRQOox2Ar9+xMszDDA4dyPnRczGXT9Vw3XD8JgXdnBLMsYArS9vWsBBiyLMAomacgBD8azPDHOB0BdIL9MM0xjK6GzkJ0wpJFi81B3pMZcWynUACIizCiKopQUr-n8CrwuASKBG2bLYjmAB6DqAAEYEoABaYAkAbbc4AAVV2aAvJnAQiDYOAAEk4FIE0CHgGBqCcFw4A2touGAHRyCI21SDgRtyHm-y4HCn4NHWzadDaXAoAg4cQoaqrovyhLp1nRzqJ1erKqa6rtgcyjF1if5yuB5qark5gk2YdMzVPc9LQ0-ytOHBGmDRtph1Kt0oEyPRMmMUq5LgLrCGjKBYwIZggA

顺便说一句,这对****来说是过度设计的;当你开始让类型像真正的代码一样工作时总是如此 ????

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

分两步使用类型安全属性访问递归解析对象 的相关文章

随机推荐

  • 从 Struts1 迁移到 Struts2 [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我的应用程序位于 Struts1 中 并且我在所有操作中都使用了 Dispatch 操作 现在请告诉我如何转换到 struts 2 以
  • Visual Studio 2012 Express 在格式文档命令上挂起并显示消息“等待后台操作完成”[重复]

    这个问题在这里已经有答案了 这件事是几天前才开始的 每次我使用 Ctrl K D 格式化文档时 VS 都会挂起并显示该消息 似乎由于某种原因 运行格式命令花费了相当长的时间 20 秒左右 有时在较大的文档上需要更长的时间 没有安装任何新的加
  • Rails 错误 - 无法加载此类文件 -- aws-sdk (您可能需要安装 aws-sdk gem)

    我有一个 RoR 应用程序 可以通过回形针和亚马逊 s3 上传图像 一切都工作正常 直到我决定将路由从 myapp com id 更改为 myapp com model name 现在我收到以下错误 LoadError cannot loa
  • 无法绑定多部分标识符[重复]

    这个问题在这里已经有答案了 可能的重复 无法绑定多部分标识符 https stackoverflow com questions 2531924 the multi part identifier could not be bound 这是
  • 在android应用程序中连接oracle

    我正在做多媒体应用 我的问题是我想通过 JDBC 将 Oracle 数据库连接到我的应用程序 每当我尝试连接 jdbc 驱动程序时 它都会显示 Dxwarning 忽略匿名内部类的 InnerClasses 属性 oracle jdbc p
  • 什么可以防止任何恶意软件在 ARM TrustZone 中引发 SMC 异常?

    我正在阅读有关 ARM TrustZone 的内容 我读到 当普通世界想要来自安全世界的功能时 他们会引发 SMC 异常 从而将数据传输到安全世界 我不明白的是 是什么阻止了任何恶意代码引发 SMC 异常 如果任何未经授权的软件都可以导致转
  • 如何让 SwiftUI 列表自动滚动?

    当向 ListView 添加内容时 我希望它自动向下滚动 我正在使用 SwiftUIList and a BindableObject作为控制器 新数据正在添加到列表中 List chatController messages id sel
  • 是否可以同时在多个画布上绘图?

    我所有的画布绘制函数都是这样开始的 function drawMe var canvas document getElementById canvas id var ctx null ctx canvas getContext 2d 但是我
  • WScript.Shell 在 FireFox 中不起作用

    我有以下 JavaScript 代码 实际上我必须在客户端启动一个exe function executeCommands var commandtoRun C WINDOWS notepad exe var oShell new Acti
  • 按自定义排序顺序对 jQuery 中的 Div 进行排序

    我正在尝试重新排序标签的子元素input通过比较 它们的类别属性对应于 Javascript 中的类别顺序 多变的category sort order 然后我需要删除其类别属性的div 没有出现在category sort order 预
  • 如何动态合并android表格布局中一个单元格中的行?

    如何通过表格布局在 android 中获得以下功能 如图所示 我需要将两个单元格合并第 5 行 第 1 列 and 行 6 列 1动态地 我必须替换那里的一张图像 我怎样才能实现这个目标 感谢您的回答 使用 GridLayout 而不是 T
  • 当用户切换电视源时在应用程序上管理 GCKDevices

    ios的Google Chromecast api有以下设备发现方法 void deviceDidComeOnline GCKDevice device manipulate UI self tableView reloadData voi
  • 从 Jenkins 管道中的 shell 步骤访问 Groovy 变量

    使用Jenkins 2 x 中的管道插件 https jenkins io doc book pipeline overview 我如何访问在阶段或节点级别的某个位置定义的 Groovy 变量 sh step 简单的例子 node stag
  • Angular JS ng-include 绑定问题

    我使用模板文件创建了一个寻呼机小部件 我在 HTML 页面中使用了两次 我有一个选择 转到页面 选项以及上一页和下一页的链接 问题是 当我使用选择框更新当前页面 然后使用上一页和下一页链接时 当前页面会更新 但选择框不会更新 请告诉我我做错
  • 类型别名和自引用

    例如 单链表节点可能定义如下 namespace example part1 class node node next int value 假设我们只有一个整数列表 此类允许有一个指向其自身类型的成员 因为无论底层数据结构的大小如何 指针都
  • Delphi 2009 函数被链接器消除

    我想使用该功能DateTimeToStr在调试项目时 我想使用评估 修改窗口或监视窗口 这总是会导致错误 函数被链接器消除 我确保该函数被项目使用 我还在我想要调试的函数中显式放置了一个调用 我还关闭了优化并重新编译了整个项目 我做了以下程
  • Android studio 和设备外部

    是否可以在 android studio 中使用外部设备模拟应用程序 我有一个设备连接到计算机 但是当您运行该应用程序时 它无法识别该设备 注意 接受未知来源的模式和调试模式已激活 转到模块的运行配置并更改 在那里 您可以选择要在模拟器 设
  • 使用深度学习突出显示句子中的重要单词

    我试图突出显示 imdb 数据集中的重要单词 这些单词最终有助于情感分析预测 数据集如下 X train 作为字符串的评论 Y train 0 或 1 现在 在使用 Glove 嵌入来嵌入 X train 值后 我可以将其输入神经网络 现在
  • Mongoose:根据 ID 从数组中删除对象(强制转换错误)

    我有一个看起来像这样的模型 mongoose Schema username String posts type Schema Types ObjectId ref Post 我有一个端点 我想传递一个 ObjectID app delet
  • 分两步使用类型安全属性访问递归解析对象

    我尝试将以下函数中的字符串类型替换为更具体的类型 以确保类型安全的属性访问 import get from lodash const obj foo foo bar a Hello b c World function factory na