Home > Enterprise >  Typescript type transform
Typescript type transform

Time:02-11

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);
    }
};
  • Related