Skip to content

Instantly share code, notes, and snippets.

@mykeels
Last active January 8, 2024 21:23
Show Gist options
  • Save mykeels/c5cded0a63397a4992567573c8c697f6 to your computer and use it in GitHub Desktop.
Save mykeels/c5cded0a63397a4992567573c8c697f6 to your computer and use it in GitHub Desktop.
Change cashing
import { camelCase, pascalCase, snakeCase } from "change-case";
type CamelToPascalCase<S extends string> = S extends `${infer F}${infer R}`
? `${Capitalize<F>}${R}`
: S;
type PascalToCamelCase<S extends string> = S extends `${infer F}${infer R}`
? `${Uncapitalize<F>}${R}`
: S;
type SnakeToCamelCase<S extends string> = S extends `${infer F}_${infer R}`
? `${Uncapitalize<F>}${Capitalize<SnakeToCamelCase<R>>}`
: S;
type CamelToSnakeCase<S extends string> = S extends `${infer F}${infer R}`
? `${Uncapitalize<F>}${R extends Capitalize<R>
? `_${Uncapitalize<R>}` : CamelToSnakeCase<R>}`
: S;
type StringKeys<TEntity extends {}> = {
[key in keyof TEntity]: key extends string ? key : never;
}[keyof TEntity];
type IsLiteral<TValue> = TValue extends string | number | boolean | symbol
? true
: false;
declare module "change-case" {
export function camelCase<TInput extends string>(
input: TInput
): SnakeToCamelCase<PascalToCamelCase<TInput>>;
export function snakeCase<TInput extends string>(
input: TInput
): CamelToSnakeCase<TInput>;
export function pascalCase<TInput extends string>(
input: TInput
): CamelToPascalCase<TInput>;
}
type Known<T, K> = unknown extends T ? unknown extends K ? never : K : T;
export type ToCamel<TInput> = TInput extends string
? SnakeToCamelCase<PascalToCamelCase<TInput>>
: IsLiteral<TInput> extends true
? TInput
: TInput extends Array<infer TItem>
? Array<ToCamel<TItem>>
: TInput extends { [key: string]: any }
? {
[key in SnakeToCamelCase<PascalToCamelCase<StringKeys<TInput>>>]: IsLiteral<
Known<TInput[CamelToSnakeCase<key>], TInput[CamelToPascalCase<key>]>
> extends true
? Known<TInput[CamelToSnakeCase<key>], TInput[CamelToPascalCase<key>]>
: ToCamel<Known<TInput[CamelToSnakeCase<key>], TInput[CamelToPascalCase<key>]>>;
}
: TInput;
/**
* Converts a string, the keys of an object, or keys of an array of objects to camelCase.
*
* @example
* toCamel('HelloWorld') // 'helloWorld'
* toCamel({ HelloWorld: 'Hello World' }) // { helloWorld: 'Hello World' }
* toCamel([{ HelloWorld: 'Hello World' }]) // [{ helloWorld: 'Hello World' }]
*/
export function toCamel<TInput>(
input: TInput
): TInput extends null | undefined ? TInput : ToCamel<TInput> {
if (!input) {
return input as any;
} else if (Array.isArray(input)) {
return input.map((i) => toCamel(i)) as any;
} else if (typeof input === "string") {
return camelCase(input) as any;
} else if (typeof input === "object") {
return Object.keys(input).reduce((result, key) => {
const literalTypes = ["string", "number", "boolean", "symbol"];
const value = (input as Record<string, any>)[key];
result[camelCase(key)] = literalTypes.includes(typeof value)
? value
: toCamel(value);
return result;
}, {} as Record<string, any>) as any;
} else {
return input as any;
}
}
type ToPascal<TInput> = TInput extends string
? CamelToPascalCase<TInput>
: IsLiteral<TInput> extends true
? TInput
: TInput extends Array<infer TItem>
? Array<ToPascal<TItem>>
: TInput extends { [key: string]: any }
? {
[key in CamelToPascalCase<StringKeys<TInput>>]: IsLiteral<
TInput[PascalToCamelCase<key>]
> extends true
? TInput[PascalToCamelCase<key>]
: ToPascal<TInput[PascalToCamelCase<key>]>;
}
: TInput;
/**
* Converts a string, the keys of an object, or keys of an array of objects to PascalCase.
*
* @example
* toPascal('helloWorld') // 'HelloWorld'
* toPascal({ helloWorld: 'Hello World' }) // { HelloWorld: 'Hello World' }
* toPascal([{ helloWorld: 'Hello World' }]) // [{ HelloWorld: 'Hello World' }]
*/
export function toPascal<TInput>(
input: TInput
): TInput extends null | undefined ? TInput : ToPascal<TInput> {
if (!input) {
return input as any;
} else if (Array.isArray(input)) {
return input.map((i) => toPascal(i)) as any;
} else if (typeof input === "string") {
return pascalCase(input) as any;
} else if (typeof input === "object") {
return Object.keys(input).reduce((result, key) => {
const literalTypes = ["string", "number", "boolean", "symbol"];
const value = (input as Record<string, any>)[key];
result[pascalCase(key)] = literalTypes.includes(typeof value)
? value
: toPascal(value);
return result;
}, {} as Record<string, any>) as any;
} else {
return input as any;
}
}
export type ToSnake<TInput> = TInput extends string
? CamelToSnakeCase<TInput>
: IsLiteral<TInput> extends true
? TInput
: TInput extends Array<infer TItem>
? Array<ToSnake<TItem>>
: TInput extends { [key: string]: any }
? {
[key in CamelToSnakeCase<StringKeys<TInput>>]: IsLiteral<
TInput[SnakeToCamelCase<key>]
> extends true
? TInput[SnakeToCamelCase<key>]
: ToSnake<TInput[CamelToSnakeCase<key>]>;
}
: TInput;
/**
* Converts a string, the keys of an object, or keys of an array of objects to snakeCase.
*
* @example
* toSnake('HelloWorld') // 'hello_world'
* toSnake({ HelloWorld: 'Hello World' }) // { hello_world: 'Hello World' }
* toSnake([{ helloWorld: 'Hello World' }]) // [{ hello_world: 'Hello World' }]
*/
export function toSnake<TInput>(
input: TInput
): TInput extends null | undefined ? TInput : ToSnake<TInput> {
if (!input) {
return input as any;
} else if (Array.isArray(input)) {
return input.map((i) => toSnake(i)) as any;
} else if (typeof input === "string") {
return snakeCase(input) as any;
} else if (typeof input === "object") {
return Object.keys(input).reduce((result, key) => {
const literalTypes = ["string", "number", "boolean", "symbol"];
const value = (input as Record<string, any>)[key];
result[snakeCase(key)] = literalTypes.includes(typeof value)
? value
: toSnake(value);
return result;
}, {} as Record<string, any>) as any;
} else {
return input as any;
}
}
type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false;
type cases = [
// Camel
Expect<Equal<ToCamel<"HelloWorld">, "helloWorld">>,
Expect<Equal<ToCamel<"hello_world">, "helloWorld">>,
Expect<
Equal<ToCamel<{ HelloWorld: "Hello World" }>, { helloWorld: "Hello World" }>
>,
Expect<
Equal<ToCamel<{ hello_world: "Hello World" }>, { helloWorld: "Hello World" }>
>,
Expect<
Equal<
ToCamel<[{ HelloWorld: "Hello World" }]>[0],
{ helloWorld: "Hello World" }
>
>,
Expect<
Equal<
ToCamel<[{ hello_world: "Hello World" }]>[0],
{ helloWorld: "Hello World" }
>
>,
Expect<
Equal<
ToCamel<[{ HelloWorld: "Hello World" }, { HelloAfrica: "Hello Africa" }]>,
(
| {
helloWorld: "Hello World";
}
| {
helloAfrica: "Hello Africa";
}
)[]
>
>,
Expect<
Equal<
ToCamel<[{ hello_world: "Hello World" }, { hello_africa: "Hello Africa" }]>,
(
| {
helloWorld: "Hello World";
}
| {
helloAfrica: "Hello Africa";
}
)[]
>
>,
// Pascal
Expect<Equal<ToPascal<"helloWorld">, "HelloWorld">>,
Expect<
Equal<
ToPascal<{ helloWorld: "Hello World" }>,
{ HelloWorld: "Hello World" }
>
>,
Expect<
Equal<
ToPascal<[{ helloWorld: "Hello World" }]>[0],
{ HelloWorld: "Hello World" }
>
>,
Expect<
Equal<
ToPascal<
[{ helloWorld: "Hello World" }, { helloAfrica: "Hello Africa" }]
>,
(
| {
HelloWorld: "Hello World";
}
| {
HelloAfrica: "Hello Africa";
}
)[]
>
>,
// Snake
Expect<Equal<ToSnake<"helloWorld">, "hello_world">>,
Expect<Equal<ToSnake<"HelloWorld">, "hello_world">>,
Expect<
Equal<ToSnake<{ helloWorld: "Hello World" }>, { hello_world: "Hello World" }>
>,
Expect<
Equal<
ToSnake<[{ helloWorld: "Hello World" }]>[0],
{ hello_world: "Hello World" }
>
>,
Expect<
Equal<
ToSnake<[{ helloWorld: "Hello World" }, { helloAfrica: "Hello Africa" }]>,
(
| {
hello_world: "Hello World";
}
| {
hello_africa: "Hello Africa";
}
)[]
>
>,
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment