While working my way through typescript types, I have a question about transformations. I have the following type:
export type DataChampLogLevel = 'debug' | 'verbose' | 'info' | 'warning' | 'error'
No it is the case that some loggers only accept debug
while others expect verbose
I have a logging facade which expects DataChampLoglevel
export interface DataChampLogger {
setLogLevel(level: DataChampLogLevel): void
}
//implementation
setLogLevel(level: DataChampLogLevel): void {
SomeLogger.setLogLevel(level) // compiler type error debug is not allowed in type OtherLogLevel..
}
internally I need to convert the value from e.g. debug
to verbose
while it is rather simple to write a convert function I was wondering if the same was possible with typesxript types e.g. String Templates or the like. I was playing around a bit but was not able to come up with the proper solution.
This is just my own curiosity to improve my skills ;)
Regards Mathias
CodePudding user response:
I was wondering if the same was possible with typesxript types
No. What you're describing is a runtime value conversion. TypeScript types don't exist at runtime (other than a small caveat around enums). So there's no TypeScript type solution to this. You'll need a runtime solution to modify a runtime value.
There may or may not be a type aspect to this as well, since SomeLogger.setLogLevel
seems to accept a different type from DataChampLogLevel
, so you'll need a way to tell TypeScript that the (updated) value is allowed for SomeLogger.setLogLevel
. But it can't help with the runtime side of it.
For instance, let's say that SomeLogger.setLogLevel
accepts a SomeLoggerLogLevel
which is:
type SomeLoggerLogLevel = 'verbose' | 'info' | 'warning' | 'error';
In that case, there's no need to do anything explicit with the type, since TypeScript's flow analysis can see you handling it inline if you do something like:
setLogLevel(level: DataChampLogLevel): void {
if (level === "debug") {
level = "verbose";
}
SomeLogger.setLogLevel(level);
}
But if you do it another way (like a lookup table), you may need to reassure TypeScript that your runtime conversion is correct. For instance, TypeScript won't be happy with this:
const logLevelMap: Partial<Record<DataChampLogLevel, SomeLoggerLogLevel>> = {
debug: "verbose",
};
const obj: DataChampLogger = {
setLogLevel(level: DataChampLogLevel): void {
level = logLevelMap[level] ?? level;
SomeLogger.setLogLevel(level); // Argument of type 'DataChampLogLevel' is not assignable to parameter of type 'SomeLoggerLogLevel'.
}
};
but if you add a type assertion function, it's happy:
const logLevelMap: Partial<Record<DataChampLogLevel, SomeLoggerLogLevel>> = {
debug: "verbose",
};
function assertsIsValidSomeLoggerLogLevel(level: DataChampLogLevel): asserts level is SomeLoggerLogLevel {
if (level in logLevelMap) {
throw new Error(`Invalid SomeLoggerLogLevel "${level}"`);
}
}
const obj: DataChampLogger = {
setLogLevel(level: DataChampLogLevel): void {
level = logLevelMap[level] ?? level;
assertsIsValidSomeLoggerLogLevel(level);
SomeLogger.setLogLevel(level);
}
};