Home > front end >  Mapping a string to Template Literal Type in TypeScript
Mapping a string to Template Literal Type in TypeScript

Time:10-29

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

Playground

This line as${Capitalize<string & P>} consists of two actions:

  1. capitalization,

  2. 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

  • Related