首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在类型记录中,如何生成KeyBy泛型类型?从对象数组中创建由特定列键确定的对象

在类型记录中,如何生成KeyBy泛型类型?从对象数组中创建由特定列键确定的对象
EN

Stack Overflow用户
提问于 2021-09-22 16:46:29
回答 2查看 620关注 0票数 0

我有一个对象类型数组,我希望将它转换为每个对象中的一个属性键控的对象。在概念上类似于Lodash的KeyBy,但在类型上。

代码语言:javascript
复制
type Arr = [
    {slug: 'test-1', value: string},
    {slug: 'test-2', value: string},
    {slug: 'test-3', value: string},
]

type KeyBy<A extends object[], S extends keyof A[number]> = {
    //1. how do I get numeric indexes of A for iteration and mapping?
    //2. how do I convert those numeric indexes to the literal types of the matching property?
}

type Keyed = KeyBy<Arr, 'slug'>

/* desired result: 
{
    'test-1': {slug: 'test-1', value: string},
    'test-2': {slug: 'test-2', value: string},
    'test-3': {slug: 'test-3', value: string},
}
*/

我认为我在KeyBy类型上设置的泛型约束是适当的,但它不需要适合这种约束。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-09-22 18:01:05

是的,这绝对是可能的

代码语言:javascript
复制
type KeyedBy<A, S extends PropertyKey> = A extends Array<infer T>
  ? KeyedByInternal<T, S>
  : never;

type KeyedByInternal<T, S extends PropertyKey> = S extends keyof T ? {
  [K in T[S] as Extends<K, PropertyKey>]: Extends<T, Record<S, K>>
} : never;

type Extends<T1, T2> = T1 extends T2 ? T1 : never;

这将使您得到您想要的所有行为,既可以用于产生文字类型的选择器,也可以用于生成“宇宙”类型的选择器(例如,stringnumber):

代码语言:javascript
复制
// Tests
type Arr = [
    {slug: 'test-1', value: string, id: 1},
    {slug: 'test-2', value: string, id: 2},
    {slug: 'test-3', value: string, id: 3},
]

type Keyed = KeyedBy<Arr, 'slug'>
/* 
{
    "test-3": {
        slug: 'test-3';
        value: string;
        id: 3;
    };
    "test-1": {
        slug: 'test-1';
        value: string;
        id: 1;
    };
    "test-2": {
        slug: 'test-2';
        value: string;
        id: 2;
    };
}
*/


type Keyed2 = KeyedBy<Arr, 'id'>
/* 
{
    3: {
        slug: 'test-3';
        value: string;
        id: 3;
    };
    1: {
        slug: 'test-1';
        value: string;
        id: 1;
    };
    2: {
        slug: 'test-2';
        value: string;
        id: 2;
    };
}
*/


// IT WORKS FOR NON-LITERAL TYPES TOO!
type Keyed3 = KeyedBy<Arr, 'value'>
/*
{
    [x: string]: {
        slug: 'test-1';
        value: string;
        id: 1;
    } | {
        slug: 'test-2';
        value: string;
        id: 2;
    } | {
        slug: 'test-3';
        value: string;
        id: 3;
    };
}
*/

真正的洞察力在于,我们可以使用Record<S, K> (其中S是表示Selector的文字类型,K是当前我们在映射类型中计算的键)过滤我们的联合类型的右侧,以便将值集中到我们处理的与选择器匹配的联合类型的分支(Es)。

票数 3
EN

Stack Overflow用户

发布于 2021-09-22 22:47:28

在这方面花费了更多的时间之后,我有了一个用数字索引解决这个问题的可行解决方案,虽然不像@Sean的解决方案那么健壮,因为它不支持通用类型,但它可能更容易理解,至少对我来说是这样。

代码语言:javascript
复制
/* helpers */
type IndecesOfArr<Arr extends any[]> = Exclude<Partial<Arr>['length'], Arr['length'] | undefined>;
type Extends<T1, T2> = T1 extends T2 ? T1 : never;

type Arr = [
    {slug: 'test-1', value: string},
    {slug: 'test-1', value: string, test: number}, //matching keys would be unioned
    {slug: 'test-2', value: string},
    {slug: 'test-3', value: string},
    {slug: ['non-indexable'], value: string}, //should be excluded from result
]

type KeyBy<A extends object[], S extends keyof A[number]> = {
    [I in IndecesOfArr<A> as Extends<A[I][S], PropertyKey>]: A[I]
}

type Keyed = KeyBy<Arr, 'slug'>

/* expected result: 
{
    'test-1': {slug: 'test-1', value: string} 
        | { slug: 'test-1', value: string, test: number},
    'test-2': {slug: 'test-2', value: string},
    'test-3': {slug: 'test-3', value: string},
}
*/

IndecesOfArr类型的工作是因为类型记录对Arr['length']的处理。当给定一个文字数组时,它实际上告诉您文字长度,而不是只告诉number。如果将Arr包装在Partial<Arr>中,它将告诉您所有可能的长度(0长度)为一个联合。我们不包括全长数,以得到所有长度的零基并。

在映射索引中,我们利用这些索引,并将索引转换为带有该索引的S属性值值的键。然后,我们将该键分配给原始数组中该索引的值。

操场连接

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69288220

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档