Derived from Max Eisenhardt's response
This function will handle recursively any snake/dashes/specified delimiter present in the key of an object. It also accounts for unrolling array types to maintain the array structures.
/** Transform a string into camel-case based on a specific delimiter */
export type CamelCaseFrom<S extends string, Delimiter extends string> = CamelCaseFromHelper<S, Delimiter>;
type CamelCaseFromHelper<S extends string, Delimiter extends string, NotFirstToken extends boolean = false> =
NotFirstToken extends true
? S extends `${infer P1}${Delimiter}${infer P2}`
? `${Capitalize<P1>}${CamelCaseFromHelper<P2, Delimiter, true>}`
: `${Capitalize<S>}`
: S extends `${infer P1}${Delimiter}${infer P2}`
? `${Lowercase<P1>}${CamelCaseFromHelper<P2, Delimiter, true>}`
: `${Lowercase<S>}`;
/** Convert keys of an object into camel-case using a specified delimiter */
export type KeysToCamelCase<T, Delimiter extends string> = {
[K in keyof T as CamelCaseFrom<string &K, Delimiter>]:
T[K] extends Array<infer ArrayElement>
? KeysToCamelCaseForArrayElement<ArrayElement, Delimiter>
: T[K] extends {}
? KeysToCamelCase<T[K], Delimiter>
: T[K];
}
/** Deals with selecting keys from nested arrays */
type KeysToCamelCaseForArrayElement<AElement, Delimiter extends string> =
AElement extends Array<infer BElement>
? Array<KeysToCamelCaseForArrayElement<BElement, Delimiter>>
: Array<KeysToCamelCase<AElement, Delimiter>>;
/* ADDITIONAL TYPINGS FOR EASE OF USE */
export type CamelCaseFromKebabCase<S extends string> = CamelCaseFrom<S, '-'>;
export type CamelCaseFromSnakeCase<S extends string> = CamelCaseFrom<S, '_'>;
Example 1
interface MyKebabCaseConfig {
'foo': string;
'foo-bar-qaz-pom-dee': string;
'nested-object': {
'first-name': string
'date-of-birth': string;
'another-one': {
'aaa-bbb-ccc': string;
};
'some-array': string[];
};
'array-with-deep-object': SomeDeepObject[][];
}
interface SomeDeepObject {
'aaa-bbb-ccc': string;
'deep-array-of-strings': string[][][][];
'deep-array-of-another-object': {
'ddd-eee-fff': string;
}[][][]
}
const transformed: KeysToCamelCase<MyKebabCaseConfig, '-'> = {
foo: '',
fooBarQazPomDee: '',
nestedObject: {
firstName: '',
dateOfBirth: '',
anotherOne: {
aaaBbbCcc: ''
},
someArray: []
},
arrayWithDeepObject: [
[
{
aaaBbbCcc: '',
deepArrayOfStrings: [ [ [ [ '' ] ] ] ],
deepArrayOfAnotherObject: [ [ [ { dddEeeFff: '' } ] ] ]
}
]
]
}
Example 2
interface BangCaseObject {
'aaa!bbb!ccc': string;
'eee!ddd': {
'fff!ggg': string;
}
}
const x: KeysToCamelCase<BangCaseObject, '!'> = {
aaaBbbCcc: '',
eeeDdd: {
fffGgg: ''
}
}