Last active
February 14, 2023 17:24
-
-
Save YouMinTW/5dcf36dd5b4bdf07fbfa2dfd27b9deab to your computer and use it in GitHub Desktop.
Change case from snake to came
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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