Is there a way to map a string to a matching template literal type in TypeScript?
Here's an example:
type SomeType<T> = {
[P in keyof T as `as${Capitalize<string & P>}`]: number;
};
This should take every property x
of T
and make it into a property asX
.
But now I'd like to take a property name like foo
and convert it into asFoo
in a way that TypeScript can detect.
I'd like to be able to do this:
interface I {
foo: number;
bar: number;
}
const obj: SomeType<I> = {
asFoo: 1,
asBar: 2,
};
const str = 'foo';
// Doesn't work, trying to capitalize str, but TypeScript only sees it as a string
obj[`as${str[0].toUpperCase()}${str.slice(1)}`];
I'd like to somehow take str
and index obj
. Is this possible somehow? I suppose I could cast the string but I'd rather not do that.
Currently I'm handling this by creating a function like this:
function getProperty(key: string): keyof SomeType<I> {
return key === 'foo' ? 'asFoo' : 'asBar';
}
obj[getProperty('foo')];
But I just want to see if there is a more elegant solution as this can get out of hand if there are a lot of properties. I'm pretty new to TypeScript so I may be missing something.
CodePudding user response:
I usually try to split my logic into smallest possible parts. COnsider this example:
type SomeType<T> = {
[P in keyof T as `as${Capitalize<string & P>}`]: number;
};
interface Obj {
foo: number;
bar: number;
}
const obj: SomeType<Obj> = {
asFoo: 1,
asBar: 2,
};
const capitalize = <Key extends string>(key: Key) =>
`${key[0].toUpperCase()}${key.slice(1)}` as Capitalize<Key>
const withPrefix = <Key extends string>(key: Key): `as${Key}` => `as${key}`
const getProperty = <Key extends string>(key: Key) => withPrefix(capitalize(key))
const result = getProperty('foo') // asFoo
const getter = obj[result] // number
This line as${Capitalize<string & P>}
consists of two actions:
capitalization,
adding
as
prefix
this is why I have used two functions for it
As you might have noticed, withPrefix
and getProperty
might be composed:
import { compose } from 'redux'
const capitalize = <Key extends string>(key: Key) =>
`${key[0].toUpperCase()}${key.slice(1)}` as Capitalize<Key>
const withPrefix = <Key extends string>(key: Key): `as${Key}` => `as${key}`
type Fn =
<Key extends string>(key: Key) => `as${Capitalize<Key>}`
const getProperty =
compose(withPrefix, capitalize) as Fn
const result = getProperty('foo') // asFoo
but it requires you to use extra compose
functions with type assertions.
You can learn more about template literal strings from docs