简体   繁体   中英

How to create an object type, with keys based on an objects values

I'm trying to turn an object into a dynamically created typing. But I'm struggling to get it right.

Let's say I have an object:

const constants = {
   filter: 'flr',
   color: 'col'
}

How would I be able to make a type, that would allow the following:

type MetaData = {
   flr: number
   col: string
}

//js
const meta: MetaData = getMeta();
const filterValue: number = meta[constants.filter];

Whenever one of the constant values changes, I want the type to automatically be updated as well.

I have tried to set this up by doing the following:

type MetaData = {
  [constants.filter]: number
  [k: constants.filter]: number
  [k: any extends constants.filter]: number
}

But nothing seems to work, and I can't seem to find a proper solution for this. Any hints for a working type?

const constants = {
  filter: 'flr',
  color: 'col'
} as const; // very important

type Constants = typeof constants; // represents the type of the constants object
type ConstantsValues = Constants[keyof Constants] // defines values of constants object
type MetaData = {
   [K in ConstantsValues]: any // map which has all keys as values of constants object
}

// using
const meta: MetaData = {
  flr: 1,
  col: 2
}
const filterValue: number = meta[constants.filter];

Explanation

Most important is to define constants as const , it means that we are saying this structure of the object is permanent and the inferred types should be precise to exactly that. Thanks to const the typeof constants defines exact keys and values which the object has.

Next things are two types Constants and ConstantsValues which are just simple definitions of types from the constants object. They are here for readability purposes.

Last is MetaData . We define it as map which keys are all values of the constants object. I set values as any as you did not define the need about this.

Any change of the constants object will effect the need of changing every instance of Metadata type.


If the whole Metadata object should have one type of value, as you have shown in your example by number , it can be achieved by:

type MetaData = {
   [K in ConstantsValues]: number // here we have a number for all values
}

or in more polymorphic way:

type MetaData<V> = {
   [K in ConstantsValues]: V // here we have V for all values
}

type MetaDataStr = MetaData<string>
type MetaDataNum = MetaData<number>

Additional need was requested in the comment. The need is to be able to define types which will represent partial of the constants object with different type of values. Here is an example of implementing that:

// bigger object to have an example
const constants = {
  filter: 'flr',
  color: 'col',
  rank: 'rnk',
  size: 'sz'
} as const;

// the same two types as previous
type Constants = typeof constants;
type ConstantsValues = Constants[keyof Constants];

// create parts of values 
type PartOfConstantsA = Extract<ConstantsValues, 'flr' | 'col'>
type PartOfConstantsB = Extract<ConstantsValues, 'rnk' | 'sz'>

// create more generic type in order to pass also keys by Keys generic
type MetaData<Keys extends PropertyKey, Values> = {
  [K in Keys]: Values
}

// we combine both types by &
type FinalMetaData = MetaData<PartOfConstantsA, number> & MetaData<PartOfConstantsB, string>;

// using
const meta: FinalMetaData = {
  flr: 1, // needs to be number
  col: 2,// needs to be number
  rnk: 'a', // needs to be string
  sz: 'b' // needs to be number
}
const filterValue = meta[constants.filter];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM