gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-05 02:31:24 24 4
gpt4 key购买 nike

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

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)];

用法: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('.'));
};
}

请允许我解释一下。首先,我在实现 NestedKeyOf 时遇到了很多麻烦 :(

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

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 不存在,那么我们将深入获取目标键,否则,我们将深入获取嵌套键和目标键。

Playground

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]T | 的联合。从不 简化为T

然后由于 namespace 是可选的,它引入了一些困难。我的一个错误是认为如果未提供 namespaceNestedKey 将是未定义的(因此之前的答案实际上是不正确的)。稍后会更正。

为了解决可选的 namespace,我们将原始 factory 函数的参数 namespace not 设为可选并重命名到 _factory(内部/私有(private))。然后我们创建一个新的 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 的对象中绕过 namespace 是可选的这一事实。将其视为委托(delegate)人。

现在修改后的 _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_factory 选择更好的名称以避免混淆,但希望您已经足够了解我的情况。

Playground

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

关于typescript - 分两步使用类型安全的属性访问递归解析对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71529277/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com