Reputation: 128
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?
Upvotes: 0
Views: 53
Reputation: 20132
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];
Upvotes: 2