用于提取嵌套对象键的类型

时间:2017-07-25 00:30:46

标签: typescript types

假设我知道某些部分数据的类型和嵌套数据结构,如下所示:

interface Element {
    foo: string;
    bar: string;
}

interface Elements {
    [name: string]: Partial<Element>;
}

interface Scopes {
    main: Elements;
    [other: string]: Elements;
}

然后我有一个函数,它接受一些Scopes类型的对象以及参数,该参数定义了我应该合并到输出对象的哪个范围(这将在没有范围的情况下展平)。该功能可能如下所示:

function merge<S extends Scopes>(obj: S, selectors?: { [K in keyof S]?: boolean }): UNKNOWNTYPE {
   // this will be some logic to extract and merge scopes
}

我在为此函数UNKNOWNTYPE正确定义输出类型时遇到问题。我想动态平坦并从输入对象中提取键,以便对于输入数据,我将获得此输出:

const scopes: Scopes = {
  main : {
    a : {
       foo: "a"
    },
    b : {
       bar: "b"
    }
  },
  other : {
    b : {
       foo : "b"
    },
    c : {
       foo : "c"
    }
  }
}

const out = merge(scopes, { main : true, other : true });

/*
out is of this strcture

{
  a : {
    foo : "a"
  },
  b : {
    foo : "b"
    bar : "b"
  },
  c : {
    foo : "c"
  }
}
*/

我尝试了一些方法并尝试了这种类型,但它并没有解决所有合并范围的键:

interface UNKNOWNTYPE<Scopes, Scope extends keyof Scopes = keyof Scopes> {
    [K in keyof Scopes[Scope]: Element]
}

有没有办法从嵌套对象中动态展平和交叉第二级对象?

1 个答案:

答案 0 :(得分:0)

好的,我喜欢这种事。首先:一般来说,这不可能做到这一点;你需要能够在类型级别枚举键,而TypeScript就不存在(现在或可能永远)。在某种程度上你可以做到这一点,你必须握住类型系统,因为它不可能自己推断出你想要它做什么。话虽如此,这是一个可能的前进方向:

按预期scopes键的次数重载该函数的次数为:

function merge<S extends Scopes, KA extends keyof S>(obj: S, selectors?: {[K in KA]?: boolean}): S[KA];
function merge<S extends Scopes, KA extends keyof S, KB extends keyof S>(obj: S, selectors?: {[K in KA | KB]?: boolean}): S[KA] & S[KB];
function merge<S extends Scopes, KA extends keyof S, KB extends keyof S, KC extends keyof S>(obj: S, selectors?: {[K in KA | KB | KC]?: boolean}): S[KA] & S[KB] & S[KC];
//... merge with 4, 5, 6, etc
function merge<S extends Scopes>(obj: S, selectors?: { [K in keyof S]?: boolean }): Elements {
   // this will be some logic to extract and merge scopes
  return null as any; // for now
}

然后,当您声明scopes时,您可能不应该将类型声明为Scopes,或者类型系统将忘记您设置的特定键。如果你只是让它推断一个类型,它将知道所有的键和值类型:

const scopes = {
  main : {
    a : {
       foo: "a"
    },
    b : {
       bar: "b"
    }
  },
  other : {
    b : {
       foo : "b"
    },
    c : {
       foo : "c"
    }
  }
}

最后,您已准备好致电merge()。您必须明确指定泛型类型。第一个是scopes变量的类型,其中包含您关注的所有键和值(仅typeof scopes,因为我们没有将其设置为Scopes),而下一个一个是selectors中的各个键:

const out = merge<typeof scopes, 'main','other'>(scopes, { main: true, other: true });
out.a.foo; 
out.a.bar; // error as expected
out.b.foo;
out.b.bar;
out.c.foo;
out.c.bar; // error as expected

你得到的out完全符合你想要的类型。所有这些箍跳都值得吗?随你(由你决定。祝你好运!