To perform this operation successfully, it is necessary to implement it at the type level since the existing built-in functions only return string
types. Therefore, generic types must be used to handle string literal types.
Let's begin with:
type StripPrefix<T extends string, Prefix extends string> =
T extends `${Prefix}${infer Result}`
? Result
: T
type Test = StripPrefix<'--prefix-foo-bar', '--prefix-'>
// ^? 'foo-bar'
This type verifies if a string literal type begins with a specified prefix and then deduces the following string literal type.
If no match is found, the type remains as T
, unaffected.
Now, we require a function to carry out this operation and enforce these types.
function removePrefix<
T extends string,
Prefix extends string
>(token: T, prefix: Prefix): StripPrefix<T, Prefix> {
return token.replace(prefix, "") as StripPrefix<T, Prefix>
}
In this function, the starting token is indicated by type T
, while the prefix to remove is represented by type Prefix
.
A type assertion with as
is needed on the return value here. This adjustment is essential because the replace
method on strings always results in the type string
. By knowing the input types, enforcement using our new StripPrefix
type can be ensured.
Another issue arises when Object.keys
consistently provides string[]
and not the specific keys. This discrepancy occurs due to potential additional keys within the actual object beyond the type's knowledge.
const obj1 = { a: 123, b: 456 }
const obj2: { a: number } = obj1 // fine
const keys = Object.keys(obj2) // ['a', 'b']
In this code snippet, although obj2
contains two keys, the type information identifies just one key.
If certainty exists that such a scenario won't transpire or won't impact the outcome significantly, an extra type assertion can accurately dictate the array of keys:
const myObjectKeys = Object.keys(tokens) as (keyof typeof tokens)[]
Subsequently, applying the preceding function to the mapped values should deliver the anticipated result.
const tokenNames = tokenKeys.map(token => removePrefix(token, "--prefix-"));
// ^? ('apple' | 'orange')[]
View Typescript playground