Mandeep Singh Bhatia
Mandeep Singh Bhatia

Reputation: 301

How to define shape of the object with dynamically generated keys and values?

I have a predefined array of strings STRINGS_ARR.

I am trying to create an object STRINGS_OBJ using the strings in that array.

Each key of the object should be dynamically generated by removing underscore character from the input string, and the value should be the input string itself.

For example: if input string is 'Some_Test_Name', then the key should be 'SomeTestName`, and the value of the key should be 'Some_Test_Name'.

I am able to write the code to fulfill the requirement and generate such an object with required key/value pairs.

But, I am not able to write type/define the structure of the object, such that TypeScript will be able to infer the value correctly for the given key.

This is what I have so far, and TypeScript is not able to infer the type of the Value, and rightly so, because the Key I am trying to extract out of InputString has been modified and is no longer matching.

type InputStringMap = {
  [Key in KeyName<InputString>]: Extract<InputString, Key>
};
type Key = keyof InputStringMap;
type Value = InputStringMap[Key];
//   ^? type Value = never

What should be the correct type/definition for such an object?

type InputString = 'Test_String_1' | 'Test_String_2';
const STRINGS_ARR: InputString[] = ['Test_String_1', 'Test_String_2'];

type KeyName<S extends string> = string extends S ? 'Error' : S extends `${infer A}_${infer B}` ? KeyName<`${A}${B}`> : S;

type InputStringMap = {
  [Key in KeyName<InputString>]: Extract<InputString, Key>
};
type Key = keyof InputStringMap;
type Value = InputStringMap[Key];
//   ^? type Value = never

const keyValuePairs = STRINGS_ARR.map<[Key, Value]>((str) => [str.replace(/_/g, '') as Key, str as Value]);
const STRINGS_OBJ = Object.fromEntries(keyValuePairs) as InputStringMap;

Playground

Upvotes: 2

Views: 28

Answers (1)

jcalz
jcalz

Reputation: 328302

The easiest way to do this is with key remapping via as:

type InputStringMap = {
  [K in InputString as KeyName<K>]: K
};

which produces

/* type InputStringMap = {
    TestString1: "Test_String_1";
    TestString2: "Test_String_2";
} */

as desired.

Playground link to code

Upvotes: 2

Related Questions