将数组映射到接口 [英] Map array to an interface
问题描述
假设我有一个如下所示的数组:
Say I have an array that looks like this:
const options = [
{
name: 'foo',
type: 'boolean'
},
{
name: 'bar',
type: 'string'
},
{
name: 'bar', // should be baz not bar
type: 'number'
}
]
我希望使用这个数组作为一个接口,它看起来像这样:
I am looking to use this array as an interface which would look something like this:
export interface Opts {
foo: boolean,
bar: string,
baz: number
}
所以这可能必须是这样的:
so that would probably have to be something like:
export type Opts = manipulate(typeof options);
其中操纵是我希望发现的一些神奇的 TS 功能.
where manipulate is some magical TS feature I hope to discover.
我相信这是一个很好的起点:https://blog.mariusschulz.com/2017/01/20/typescript-2-1-mapped-types
I believe this is a good place to start: https://blog.mariusschulz.com/2017/01/20/typescript-2-1-mapped-types
但很难弄清楚.
推荐答案
Yes, you can do this, but it requires both mapped and conditional types.
首先,您需要一个类型来表示从像 "boolean"
这样的类型名称到像 boolean
这样的实际类型的映射.
First you need a type that represents your mapping from type names like "boolean"
, to actual types like boolean
.
type TypeMapping = {
boolean: boolean,
string: string,
number: number,
// any other types
}
然后你需要一个辅助函数来确保你的 options
值不会得到它的 name
和 type
属性的类型扩展到<代码>字符串代码>.(如果你检查你的 options
值,它的类型类似于 {name: string, type: string}[]
,它已经丢失了特定的 name
和 type
值.)您可以使用 通用约束来做到这一点,如下:
Then you need a helper function which makes sure your options
value doesn't get its types for the name
and type
properties widened to string
. (If you inspect your options
value, its type is something like {name: string, type: string}[]
, which has lost track of the particular name
and type
values you want.) You can use generic constraints to do this, as follows:
const asOptions = <K extends keyof any,
T extends Array<{ name: K, type: keyof TypeMapping }>>(t: T) => t;
让我们看看它是否有效:
Let's see if it works:
const options = asOptions([
{
name: 'foo',
type: 'boolean'
},
{
name: 'bar',
type: 'string'
},
{
name: 'bar',
type: 'number'
}
]);
如果你检查你会发现它现在是一个类型数组,其中每个 name
和 type
都被缩小到文字 "foo"
、"bar"
、"number"
等
If you inspect that you will see that it is now an array of types where each of name
and type
are narrowed to the literals "foo"
, "bar"
, "number"
, etc.
最后我们必须做你想要的 manipulate
类型的函数.我称之为OptionsToType
:
Finally we have to do that manipulate
type function you want. I'll call it OptionsToType
:
type OptionsToType<T extends Array<{ name: keyof any, type: keyof TypeMapping }>>
= { [K in T[number]['name']]: TypeMapping[Extract<T[number], { name: K }>['type']] }
这可能看起来很复杂.看看能不能破解
That might seem very complicated. Let's see if I can break it down.
T extends Array<{ name: keyof any, type: keyof TypeMapping }>
表示 T
必须是一个对象数组,其中 name
字段像对象键一样,type
字段像对象键一样TypeMapping
在上面输入.
means T
must be an array of objects with a name
field like an object key, and a type
field like a key of the TypeMapping
type above.
= { [K in T[number]['name']]: ... }
从 T
数组的每个元素遍历 name
属性中的所有键名
iterate through all the key names in the name
property from each element of the T
array
Extract<T[number], { name: K }>
的意思是找到T
中对应名称K
的元素"...
means "find the element of T
that corresponds to the name K
"...
Extract<T[number], { name: K }>['type']
...并查找它的 'type'
属性...
...and look up its 'type'
property...
TypeMapping[Extract<T[number], { name: K }>['type']]
...并将其用作 TypeMapping
类型的索引.
...and use that as an index into the TypeMapping
type.
好的,让我们看看它是否有效:
Okay let's see if it works:
export type Opts = OptionsToType<typeof options>;
如果你检查 Opts
你会看到:
And if you inspect Opts
you see:
{
foo: boolean;
bar: string | number;
}
正如你所期望的---呃,等等,为什么 bar
属性的类型是 string |号码
?哦,因为您将 bar
放在 options
中两次.将第二个更改为 baz
,这将是您所期望的.
just as you expected--- uh, wait, why is the bar
property of type string | number
? Oh, because you put bar
in options
twice. Change the second one to baz
and it will be what you expect.
好的,希望有帮助.祝你好运!
Okay, hope that helps. Good luck!
这篇关于将数组映射到接口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!