将对象转换为KV值的数组



我想将一个对象转换为Key/Value配对的数组。例如:

const dict = { foo: 1, bar: 2, baz: "hi" };
const arr = dictToArray(dict);

其中arr将被转换为类型的强表示,看起来像:

[
["foo", { foo: 1 }],
["bar", { bar: 2 }],
["baz", { baz: "hi" }]
]

从运行时的角度来看,这很简单,但我很难让键入内容跟上进度。我的转换实现是:

export function dictToArray<T extends NonNullable<{}>>(obj: T): DictArray<T> {
const out: any = [];
for (const key of Object.keys(obj)) {
out.push([key, { [key]: obj[key as keyof T] }]);
}
return out as DictArray<T>;
}

公用设施功能/类型:

export type DictArray<T> = [keyof T, Pick<T, keyof T>][];

我认为失败的是DictArray的定义,尤其是Pick引用。我这么说是因为数组中的每个元组都由正确的运行时数据组成,但元组的第二个元素,即键值对,被类型化为完整对象T

在对象赋值中,我会用{[K in keyof T]: V }类型的赋值映射到每个道具上,但我不确定数组的可比语法是什么。我猜答案就在那里。

打字游戏场

请注意,所需的dictToArray函数类型可能会与Why dons';t Object.keys返回TypeScript?中类型的键?,TypeScript中的对象类型open,并且值在运行时可以包含比编译器已知的更多的属性。此:

function keys<T extends object>(obj: T) {
return Object.keys(obj) as Array<keyof T>;
}

当你向它传递一个对象文字时,它会很好地工作,但如果你向它发送一个用额外属性扩展的类或接口的实例,可能会产生意想不到的结果。只要你意识到这个问题,并愿意面对后果,那就太好了。


无论如何,我在这里对dictToArray()打字的倾向是这样的:

type DictArray<T extends object> = 
Array<{ [K in keyof T]: [K, Pick<T, K>] }[keyof T]>;
declare function dictToArray<T extends object>(obj: T): DictArray<T>;

我们正在制作一个映射类型,它遍历keyof T中的每个键K,并为其生成元组[K, Pick<T, K>]。如果T看起来像{x: 1, y: 2},这将生成类似{x: ["x", {x: 1}], y: ["y", {y: 2}]}的内容,它具有您想要的属性值,但仍然嵌套在对象类型中。。。所以我们用CCD_ 13索引到这个对象中以获得所需的并集CCD_。

你可以验证这给了你一个合理的类型:

const arr = dictToArray(dict);
/* const arr: DictArray<{
foo: number;
bar: number;
baz: string;
}> */

哦,好吧,这实际上并没有显示太多。让我们强迫编译器扩展它:

type ExpandRecursively<T> = 
T extends object ? { [K in keyof T]: ExpandRecursively<T[K]> } : T;
type ArrType = ExpandRecursively<typeof arr>;
/* type ArrType = (
["foo", { foo: number; }] | 
["bar", { bar: number; }] | 
["baz", { baz: string; }]
)[] */

所以,你可以看到arr有一个类型,它是一个强类型元组的数组。好极了


哦,等等,我知道你想要这种类型:

[
["foo", { foo: 1 }],
["bar", { bar: 2 }],
["baz", { baz: "hi" }]
]

除了编译器不知道对象属性的文字值这一事实之外(您让编译器推断类型,以便将它们扩展为numberstring,而不是12"hi"(。。。呃,我跑题了。


我知道你想要这种类型:

[
["foo", { foo: number }],
["bar", { bar: number }],
["baz", { baz: string }]
]

这似乎代表了出现的条目的顺序。从技术上讲,对象属性顺序在JavaScript中是可以观察到的,尽管它并不总是按照人们期望的方式行事(JavaScript保证对象属性顺序吗?(。但在TypeScript类型中,对象属性顺序是,不应该是可观察的。类型{foo: number, bar: number, baz: string}等价于类型{bar: number, baz: string, foo: number}。你不应该做任何类型系统测试来区分两者。有时可以从类型系统中提取订单信息,但这是一个糟糕的想法,因为它是不确定的(如何将并集类型转换为元组类型(。

因此,没有原则性的方法可以要求编译器为您提供上述类型,而不是

[
["foo", { foo: number }],
["baz", { baz: string }],
["bar", { bar: number }]
]

或任何其他排序。在可想象的范围内,我们可以使DictArray<T>为您提供所有可能的排序的并集,但这将非常严重,甚至可能不特别适用于小情况。所以,除非你病态地好奇如何生成它,否则我不会打扰你。


到代码的游乐场链接

最新更新