So, I want to create a function that would take a duration string (e.g 12ms
, 7.5 MIN
, 400H
), parse it and convert it to milliseconds.
const units = {
MS: 1,
S: 1 * 1000,
MIN: 60 * 1 * 1000,
H: 60 * 60 * 1 * 1000
}
export function toMS(str: string): number {
let regex = /^(?<number>\d+(?:\.\d+)?)(?:\s*(?<unit>MS|S|MIN|H))?$/i
if (!regex.test(str)) {
throw new TypeError(`Expected a duration, got: ${str}`)
}
let match = str.match(regex)
let number = Number(match?.groups?.number)
let unit = (match?.groups?.unit || 'ms').toUpperCase()
return Math.floor(number * units[unit])
}
So, function toMS()
takes a string, tests if the provided string is valid (number
+ whitespace (optional)
+ unit abbr (optional)
) and if it's valid - parses it using str.match(regex)
.
Everything works fine until: units[unit]
. It gives me an error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type
.
I've had the same error before with function arguments & class constructors and it was easy to solve since the user provides data. But now I have no idea how to solve that, cause I can't force str.match(regex).groups.unit
to have a specific type like : 'MS | S | MIN | H
.
I know that it is also possible to create tsconfig.json with "noImplicitAny": false
but in my case that's not good at all.
The problem is that TypeScript has no way of knowing whether your regex will really match MS|S|MIN|H
. This is dependent types territory, and TypeScript is not that powerful yet.
The only thing TypeScript knows is that what you've matched is going to be a string
, because both match?.groups?.unit
and 'ms'
expressions yield string
.
What you can do is letting TypeScript know that your units
is an object with keys of type string
and values of type number
, and check whether what you've matched is a property on the units
object. Like this:
const units: { [k: string]: number } = { // letting TypeScript know
MS: 1,
S: 1 * 1000,
MIN: 60 * 1 * 1000,
H: 60 * 60 * 1 * 1000,
};
export function toMS(str: string): number {
const regex = /^(?<number>\d+(?:\.\d+)?)(?:\s*(?<unit>MS|S|MIN|H))?$/i;
if (!regex.test(str)) {
throw new TypeError(`Expected a duration, got: ${str}`);
}
const match = str.match(regex);
const number = Number(match?.groups?.number);
const unit = (match?.groups?.unit || 'ms').toUpperCase();
if (unit in units) { // checking whether your matched string is something you can handle, in runtime
return Math.floor(number * units[unit]);
} else {
throw new Error(`couldn't find units! :(`);
}
}