问题
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);
where manipulate is some magical TS feature I hope to discover.
I believe this is a good place to start: https://blog.mariusschulz.com/2017/01/20/typescript-2-1-mapped-types
but it's hard to figure out.
回答1:
Yes, you can do this, but it requires both mapped and conditional types.
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
}
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'
}
]);
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.
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 }>
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']]: ... }
iterate through all the key names in the name
property from each element of the T
array
Extract<T[number], { name: K }>
means "find the element of T
that corresponds to the name K
"...
Extract<T[number], { name: K }>['type']
...and look up its 'type'
property...
TypeMapping[Extract<T[number], { name: K }>['type']]
...and use that as an index into the TypeMapping
type.
Okay let's see if it works:
export type Opts = OptionsToType<typeof options>;
And if you inspect Opts
you see:
{
foo: boolean;
bar: string | number;
}
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!
来源:https://stackoverflow.com/questions/51334324/map-array-to-an-interface