I have a a basic enum decribing log levels, it's variants are specifically made to be numbers to be able to conviniently handle log levels using <=
operator
enum LogLevel {
NONE,
ERROR,
WARNING,
INFO,
DEBUG,
}
I still have a need for string names of log levels to be able to display them in log messages, so I create a type, which can be indexed by LogLevel
and will automatically fill an object fields with the names corresponding to LogLevel
keys like so:
type LogLevelNames = {
[K in LogLevel]: string;
};
// -- Issue here -> `string` can hold anything
const LOG_LEVEL_NAMES: LogLevelNames = {
[LogLevel.NONE]: '',
[LogLevel.ERROR]: '',
[LogLevel.WARNING]: '',
[LogLevel.INFO]: '',
[LogLevel.DEBUG]: '',
};
It works, but as seen in example nothing obliges string values to have real meaningful names, let's change a type a bit:
export type LogLevelNames = {
[K in LogLevel]: keyof typeof LogLevel;
};
// -- Another issue -> key/value types do not correlate
const LOG_LEVEL_NAMES: LogLevelNames = {
[LogLevel.NONE]: "NONE",
[LogLevel.ERROR]: "NONE",
[LogLevel.WARNING]: "NONE",
[LogLevel.INFO]: "NONE",
[LogLevel.DEBUG]: "NONE"
};
Better already - the keys are now forced to match those of LogLevel
, but they can match any of them
Here's where I'm stuck - it feels like I need to either have a way to infer a correlating type inside of an index signature, which I don't think is possible or have a way to apply keyof typeof
to K
in LogLevel
context, which I didn't succeed in as well.
Question: Is there a way to achieve something like that?
For ease of discussion let's give a name to typeof LogLevel
, the actual LogLevel
object:
type LogLevelObj = typeof LogLevel
Now, that type is more or less what you're looking for, except that the keys are your desired values and the values are your desired keys. You can use key remapping in a mapped type to switch those, like this:
type LogLevelNames = {
[K in keyof LogLevelObj as LogLevelObj[K]]: K;
};
That is, for each key K
of LogLevelObj
whose value is LogLevelObj[K]
, the key of the new property is LogLevelObj[K]
and the value is K
. That gives you
/* type LogLevelNames = {
[x: string]: number;
readonly 0: "NONE";
readonly 1: "ERROR";
readonly 2: "WARNING";
readonly 3: "INFO";
readonly 4: "DEBUG";
} */
Hmm, there's a string
index signature in there. That's because numeric enums have reverse mappings and the type LogLevelObj
has a numeric index signature like {[k: number]: string}
to represent the reverse mapping. We really don't want that in our final type, because {[k: string]: number}
messes up the rest of it.
Let's modify LogLevelNames
so that we skip the numeric index signature, by intersecting keyof LogLevelObj
with string
:
type LogLevelNames = {
[K in string & keyof LogLevelObj as LogLevelObj[K]]: K;
};
Now the type is
/* type LogLevelNames = {
readonly 0: "NONE";
readonly 1: "ERROR";
readonly 2: "WARNING";
readonly 3: "INFO";
readonly 4: "DEBUG";
} */
which is what you wanted.