Skip to content

Instantly share code, notes, and snippets.

@YouMinTW
Last active February 14, 2023 17:24
Show Gist options
  • Save YouMinTW/5dcf36dd5b4bdf07fbfa2dfd27b9deab to your computer and use it in GitHub Desktop.
Save YouMinTW/5dcf36dd5b4bdf07fbfa2dfd27b9deab to your computer and use it in GitHub Desktop.
Change case from snake to came
// typescript version is 4.6.3
// type CamelCase<S extends string> = S extends `${infer P1}_${infer P2}${infer P3}`
// ? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
// : Lowercase<S>
// type CamelCase<S extends string> = S extends `${infer T}_${infer U}` ? `${T}${Capitalize<CamelCase<U>>}` : S;
// import {CamelCase} from 'type-fest'
// Opaque reference from: https://blog.beraliv.dev/2021-05-07-opaque-type-in-typescript
declare const opaque: unique symbol;
type Opaque<K, T> = K & { readonly [opaque]: T };
type IncrementalId = Opaque<number, 'id'>;
// type AccountNumber = Opaque<number, 'accountNumber'>;
// CamelCase below is reference from type-fest@2.9.0
type WordSeparators = '-' | '_' | ' ';
type Split<S extends string, Delimiter extends string> = S extends `${infer Head}${Delimiter}${infer Tail}`
? [Head, ...Split<Tail, Delimiter>]
: S extends Delimiter
? []
: [S];
type InnerCamelCaseStringArray<Parts extends readonly any[], PreviousPart> = Parts extends [
`${infer FirstPart}`,
...infer RemainingParts
]
? FirstPart extends undefined
? ''
: FirstPart extends ''
? InnerCamelCaseStringArray<RemainingParts, PreviousPart>
: `${PreviousPart extends '' ? FirstPart : Capitalize<FirstPart>}${InnerCamelCaseStringArray<
RemainingParts,
FirstPart
>}`
: '';
type CamelCaseStringArray<Parts extends readonly string[]> = Parts extends [
`${infer FirstPart}`,
...infer RemainingParts
]
? Uncapitalize<`${FirstPart}${InnerCamelCaseStringArray<RemainingParts, FirstPart>}`>
: never;
type CamelCase<K> = K extends string
? CamelCaseStringArray<Split<K extends Uppercase<K> ? Lowercase<K> : K, WordSeparators>>
: K;
const isArray = (a: unknown) => Array.isArray(a);
// const isPlainObject = (a: unknown) => typeof a === 'object' && a !== null;
// reference from: https://blog.nonotw.com/2020/03/09/javascript-is-plain-object/
const isPlainObject = (obj: unknown) => Object.prototype.toString.call(obj) === '[object Object]';
const isNotPrimitive = (a: unknown): a is Record<string, any> => {
return isPlainObject(a) || isArray(a);
};
type CamelCasedPropertiesDeep<Value> = Value extends Function
? Value
: Value extends Array<infer U>
? Array<CamelCasedPropertiesDeep<U>>
: Value extends Set<infer U>
? Set<CamelCasedPropertiesDeep<U>>
: {
/**
* The following line of code transforms the object's keys that match the pattern 'id' or *_id
* into camel case format. This ensures that only properties that end with 'id' or *_id
* are considered, avoiding any misinterpretation of similar names such as 'stupid', 'identity', or 'aida'.
*/
[K in keyof Value as CamelCase<K>]: K extends 'id' | `${string}_id`
? /**
* If the key ends with 'id' or *_id, it will avoid calling the function recursively,
* instead, it will use the original value Value[K] directly.
* This is because it could be an Id Type or an IncrementalId Type,
* and these types may be implemented as Opaque types which cannot be called recursively.
*/
Value[K]
: CamelCasedPropertiesDeep<Value[K]>;
};
export function toCamelCase<T extends Record<string, any>>(obj: T): CamelCasedPropertiesDeep<T> {
if (isArray(obj)) {
return obj.map((elem: unknown) => (isNotPrimitive(elem) ? toCamelCase(elem) : elem)) as CamelCasedPropertiesDeep<T>;
}
const newObj = {} as CamelCasedPropertiesDeep<T>;
for (const [key, value] of Object.entries(obj)) {
const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
if (isPlainObject(value)) {
newObj[camelKey] = toCamelCase(value);
} else if (isArray(value)) {
newObj[camelKey] = value.map(elem => (isNotPrimitive(elem) ? toCamelCase(elem) : elem));
} else {
newObj[camelKey] = value;
}
}
return newObj;
}
const data = toCamelCase({ a_bb_cc: { ea_ea: 'La', user_id: 2 as IncrementalId } });
console.log(data);
/**
const data: {
aBbCc: {
eaEa: string;
userId: IncrementalId;
};
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment