Skip to content

Instantly share code, notes, and snippets.

@mykeels
Created May 30, 2023 23:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mykeels/c7f2c5b6259ca0a3daf77b751c4af2d1 to your computer and use it in GitHub Desktop.
Save mykeels/c7f2c5b6259ca0a3daf77b751c4af2d1 to your computer and use it in GitHub Desktop.
Typings for the snake-camel npm package https://www.npmjs.com/package/snake-camel
type CamelToSnakeCase<S extends string> = S extends `${infer T}${infer U}`
? `${T extends Capitalize<T> ? '_' : ''}${Lowercase<T>}${CamelToSnakeCase<U>}`
: S
type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}`
? `${T}${Capitalize<SnakeToCamelCase<U>>}`
: 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 'snake-camel' {
function camel<TInput>(input: TInput): SnakeToCamelCase<TInput>
function snake<TInput>(input: TInput): CamelToSnakeCase<TInput>
function toCamel<TInput>(input: TInput): TInput extends any[]
? toCamel<TInput[number]>
: TInput extends {}
? {
[key in SnakeToCamelCase<StringKeys<TInput>>]: IsLiteral<
TInput[CamelToSnakeCase<key>]
> extends true
? TInput[CamelToSnakeCase<key>]
: toCamel<TInput[CamelToSnakeCase<key>]>
}
: TInput
function toSnake<TInput>(input: TInput): TInput extends any[]
? toSnake<TInput[number]>
: TInput extends {}
? {
[key in CamelToSnakeCase<StringKeys<TInput>>]: IsLiteral<
TInput[SnakeToCamelCase<key>]
> extends true
? TInput[SnakeToCamelCase<key>]
: toSnake<TInput[SnakeToCamelCase<key>]>
}
: TInput
}
@mykeels
Copy link
Author

mykeels commented Jun 11, 2023

import { camelCase, pascalCase } 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 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;

type ToCamel<TInput> = TInput extends string
  ? PascalToCamelCase<TInput>
  : IsLiteral<TInput> extends true
  ? TInput
  : TInput extends Array<infer TItem>
  ? Array<ToCamel<TItem>>
  : TInput extends { [key: string]: any }
  ? {
      [key in PascalToCamelCase<StringKeys<TInput>>]: IsLiteral<
        TInput[CamelToPascalCase<key>]
      > extends true
        ? TInput[CamelToPascalCase<key>]
        : ToCamel<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) => {
      result[camelCase(key)] = (input as Record<string, any>)[key];
      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) => {
      result[pascalCase(key)] = (input as Record<string, any>)[key];
      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;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
type cases = [
  // Camel
  Expect<Equal<ToCamel<'HelloWorld'>, 'helloWorld'>>,
  Expect<Equal<ToCamel<{ HelloWorld: 'Hello World' }>, { helloWorld: 'Hello World' }>>,
  Expect<Equal<ToCamel<[{ HelloWorld: 'Hello World' }]>[0], { helloWorld: 'Hello World' }>>,
  Expect<
    Equal<
      ToCamel<[{ HelloWorld: 'Hello World' }, { HelloAfrica: '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';
          }
      )[]
    >
  >
];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment