You can use the following TS features to accomplish this:
We'll split it by using a generic Split
type, and split by the
(space) character, replacing it with _
(underscore). In this case we use a recursive strategy to accomplish this.
I borrowed the Split
type from type-fest
, and inspired it my answer from their various change case types
// Taken from type-fest https://github.com/sindresorhus/type-fest/blob/main/source/split.d.ts
type Split<
S extends string,
Delimiter extends string,
> = S extends `${infer Head}${Delimiter}${infer Tail}`
? [Head, ...Split<Tail, Delimiter>]
: S extends Delimiter
? []
: [S];
type ConvertSpaceToUnderscore<Parts extends readonly any[], PreviousPart = never> =
Parts extends [`${infer FirstPart}`, ...infer RemainingParts]
? FirstPart extends undefined
? ''
: FirstPart extends ''
? ConvertSpaceToUnderscore<RemainingParts, PreviousPart>
: RemainingParts extends {length: 0} ? Lowercase<FirstPart> : `${Lowercase<FirstPart>}_${ConvertSpaceToUnderscore<RemainingParts, PreviousPart>}`
: '';
type Convert<T extends Record<string, string>> = {
[K in keyof T as
K extends string ? ConvertSpaceToUnderscore<Split<K, ' '>> : never
]: T[K]
}
Then use it on your type
type Input = {
'FIRST NAME': 'John',
'last_name' : 'Doe',
'Email Address': 'John.Doe@example.com'
}
type Converted = Convert<Input>
// =>
{
first_name: "John";
last_name: "Doe";
email_address: "John.Doe@example.com"
}
See it in action on TS Playground
You can add your own splitters as needed to convert any other characters.
This type then can be trivially applied to any implementation to object-to-object mapper utility as the return value.