Map array to an interface

对着背影说爱祢 提交于 2019-12-09 13:41:59

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!