UNPKG

camelcase-keys

Version:
249 lines (207 loc) 6.45 kB
import type {CamelCase, PascalCase} from 'type-fest'; // Type that accepts both interfaces and type aliases // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style type ObjectLike = {[key: string]: any}; // Helper to check if a key is in the exclude list type IsExcluded<K, Exclude extends readonly unknown[]> = Exclude extends readonly never[] ? false : Exclude extends readonly [infer First, ...infer Rest] ? K extends First ? true : IsExcluded<K, Rest> : false; // Helper to check if a path should stop transformation type IsStopPath<Path extends string, StopPaths extends readonly string[]> = StopPaths extends readonly never[] ? false : StopPaths extends readonly [infer First, ...infer Rest extends readonly string[]] ? Path extends First ? true : IsStopPath<Path, Rest> : false; // Build dot-notation path type AppendPath<Base extends string, Key extends string> = Base extends '' ? Key : `${Base}.${Key}`; /** Convert keys of an object to camelcase strings. Note: Opaque types from `type-fest` may expose underlying primitive methods in the result type due to TypeScript's mapped type distribution. Runtime behavior is correct. Workaround: Use `result.key as OpaqueType` if needed. */ export type CamelCaseKeys< T extends ObjectLike | readonly unknown[], Deep extends boolean = false, IsPascalCase extends boolean = false, PreserveConsecutiveUppercase extends boolean = false, Exclude extends readonly unknown[] = readonly never[], StopPaths extends readonly string[] = readonly never[], Path extends string = '', > = T extends readonly any[] ? // Handle arrays {[K in keyof T]: ProcessArrayElement<T[K], Deep, IsPascalCase, PreserveConsecutiveUppercase, Exclude, StopPaths>} : T extends ObjectLike ? // Handle objects { [K in keyof T as IsExcluded<K, Exclude> extends true ? K : IsPascalCase extends true ? PascalCase<K> : CamelCase<K, {preserveConsecutiveUppercase: PreserveConsecutiveUppercase}> ]: ProcessValue<T[K], K & string, Path, Deep, IsPascalCase, PreserveConsecutiveUppercase, Exclude, StopPaths> } : T; // Return non-objects as-is // Process a value, checking if we should recurse type ProcessValue< V, K extends string, Path extends string, Deep extends boolean, IsPascalCase extends boolean, PreserveConsecutiveUppercase extends boolean, Exclude extends readonly unknown[], StopPaths extends readonly string[], > = IsStopPath<AppendPath<Path, K>, StopPaths> extends true ? V // Stop recursion at this path : Deep extends true ? V extends ObjectLike | readonly any[] ? CamelCaseKeys<V, Deep, IsPascalCase, PreserveConsecutiveUppercase, Exclude, StopPaths, AppendPath<Path, K>> : V : V; // Process array elements type ProcessArrayElement< E, Deep extends boolean, IsPascalCase extends boolean, PreserveConsecutiveUppercase extends boolean, Exclude extends readonly unknown[], StopPaths extends readonly string[], > = Deep extends true ? E extends ObjectLike | readonly any[] ? CamelCaseKeys<E, Deep, IsPascalCase, PreserveConsecutiveUppercase, Exclude, StopPaths> : E : E extends ObjectLike ? CamelCaseKeys<E, false, IsPascalCase, PreserveConsecutiveUppercase, Exclude, StopPaths> : E; export type Options = { /** Exclude keys from being camel-cased. If this option can be statically determined, it's recommended to add `as const` to it. @default [] */ readonly exclude?: ReadonlyArray<string | RegExp>; /** Recurse nested objects and objects in arrays. @default false @example ``` import camelcaseKeys from 'camelcase-keys'; const object = { 'foo-bar': true, nested: { unicorn_rainbow: true } }; camelcaseKeys(object, {deep: true}); //=> {fooBar: true, nested: {unicornRainbow: true}} camelcaseKeys(object, {deep: false}); //=> {fooBar: true, nested: {unicorn_rainbow: true}} ``` */ readonly deep?: boolean; /** Uppercase the first character: `bye-bye` → `ByeBye` @default false @example ``` import camelcaseKeys from 'camelcase-keys'; camelcaseKeys({'foo-bar': true}, {pascalCase: true}); //=> {FooBar: true} camelcaseKeys({'foo-bar': true}, {pascalCase: false}); //=> {fooBar: true} ```` */ readonly pascalCase?: boolean; /** Preserve consecutive uppercase characters: `foo-BAR` → `FooBAR` @default false @example ``` import camelcaseKeys from 'camelcase-keys'; camelcaseKeys({'foo-BAR': true}, {preserveConsecutiveUppercase: true}); //=> {fooBAR: true} camelcaseKeys({'foo-BAR': true}, {preserveConsecutiveUppercase: false}); //=> {fooBar: true} ```` */ readonly preserveConsecutiveUppercase?: boolean; /** Exclude children at the given object paths in dot-notation from being camel-cased. For example, with an object like `{a: {b: '🦄'}}`, the object path to reach the unicorn is `'a.b'`. If this option can be statically determined, it's recommended to add `as const` to it. @default [] @example ``` import camelcaseKeys from 'camelcase-keys'; const object = { a_b: 1, a_c: { c_d: 1, c_e: { e_f: 1 } } }; camelcaseKeys(object, { deep: true, stopPaths: [ 'a_c.c_e' ] }), // { // aB: 1, // aC: { // cD: 1, // cE: { // e_f: 1 // } // } // } ``` */ readonly stopPaths?: readonly string[]; }; /** Convert object keys to camel case using [`camelcase`](https://github.com/sindresorhus/camelcase). @param input - Object or array of objects to camel-case. @example ``` import camelcaseKeys from 'camelcase-keys'; // Convert an object camelcaseKeys({'foo-bar': true}); //=> {fooBar: true} // Convert an array of objects camelcaseKeys([{'foo-bar': true}, {'bar-foo': false}]); //=> [{fooBar: true}, {barFoo: false}] ``` @example ``` import {parseArgs} from 'node:util'; import camelcaseKeys from 'camelcase-keys'; const commandLineArguments = parseArgs(); //=> {_: [], 'foo-bar': true} camelcaseKeys(commandLineArguments); //=> {_: [], fooBar: true} ``` */ export default function camelcaseKeys< T extends ObjectLike | readonly ObjectLike[], O extends Options = Options, >( input: T, options?: O ): CamelCaseKeys< T, O['deep'] extends true ? true : false, O['pascalCase'] extends true ? true : false, O['preserveConsecutiveUppercase'] extends true ? true : false, O['exclude'] extends readonly unknown[] ? O['exclude'] : readonly never[], O['stopPaths'] extends readonly string[] ? O['stopPaths'] : readonly never[] >;