deep-case-crafter 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.cts +53 -22
- package/dist/index.d.mts +53 -22
- package/dist/index.d.ts +53 -22
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +4 -2
- package/readme.md +77 -35
package/dist/index.d.cts
CHANGED
|
@@ -15,23 +15,30 @@ type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
|
|
|
15
15
|
type IsStringLiteral<S extends string> = Record<never, never> extends Record<S, never> ? false : true;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Represents the supported string case formats for transformation
|
|
18
|
+
* Represents the supported string case formats for transformation. `'constant'` is
|
|
19
|
+
* CONSTANT_CASE (`USER_ID`), a target case only: keys such as `USER_ID` are left
|
|
20
|
+
* unchanged by every target case.
|
|
19
21
|
*/
|
|
20
|
-
type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
|
|
22
|
+
type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab' | 'constant';
|
|
21
23
|
/**
|
|
22
24
|
* The plain literal of a target case: a string enum member or a branded literal such as
|
|
23
25
|
* `'snake' & { __brand: 'Case' }` becomes `'snake'`, so it compares equal to the case
|
|
24
26
|
* detected from a key. Only literals are produced, never `To` itself: TypeScript 4.7
|
|
25
27
|
* narrows `To` to `To & StringCase` in a true branch, which is `never` for an enum member.
|
|
28
|
+
*
|
|
29
|
+
* Not meant to be used directly. It is exported so that declaration files of code with a
|
|
30
|
+
* generic target case refer to it instead of writing out the list of target cases, which
|
|
31
|
+
* would leave out cases added later.
|
|
26
32
|
*/
|
|
27
|
-
type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
|
|
33
|
+
type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : To extends 'constant' ? 'constant' : never;
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* Options for the transformKey function. The transform function takes them as well.
|
|
31
37
|
*/
|
|
32
38
|
interface TransformKeyOptions {
|
|
33
39
|
/**
|
|
34
|
-
* The target casing format to transform to
|
|
40
|
+
* The target casing format to transform to. `'constant'` is CONSTANT_CASE, such as
|
|
41
|
+
* `USER_ID`.
|
|
35
42
|
* @default 'camel'
|
|
36
43
|
*/
|
|
37
44
|
targetCase?: StringCase | undefined;
|
|
@@ -67,6 +74,23 @@ interface TransformOptions extends TransformKeyOptions {
|
|
|
67
74
|
* @default []
|
|
68
75
|
*/
|
|
69
76
|
exclude?: readonly string[] | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Whether an object reached through several references gives one result, as in the
|
|
79
|
+
* input: shared objects stay shared, and circular references point back into the
|
|
80
|
+
* result. Pass `false` for data that cannot contain such references, such as the
|
|
81
|
+
* output of `JSON.parse`: the transform then skips this bookkeeping, which makes inputs
|
|
82
|
+
* with many objects and arrays up to about twice as fast.
|
|
83
|
+
*
|
|
84
|
+
* Use it only for tree-shaped data. An object is then copied once for every path that
|
|
85
|
+
* leads to it, so shared objects inside shared objects multiply. Data with more than
|
|
86
|
+
* 256 nested objects, arrays, Maps and Sets on one path, as circular references always
|
|
87
|
+
* have, is converted again from the start with the bookkeeping, so it takes longer and
|
|
88
|
+
* getters can run twice. With a finite `depth`, circular references stop at the depth
|
|
89
|
+
* limit before that, and are copied once per path down to it.
|
|
90
|
+
*
|
|
91
|
+
* @default true
|
|
92
|
+
*/
|
|
93
|
+
keepReferences?: boolean | undefined;
|
|
70
94
|
/**
|
|
71
95
|
* @deprecated Has no effect. Every key's case is detected from the key itself, both at
|
|
72
96
|
* runtime and in the result type, so there is nothing to declare.
|
|
@@ -92,7 +116,13 @@ type NoUnknownOptions<Options, Known = TransformOptions> = {
|
|
|
92
116
|
type NoUnknownKeyOptions<Options> = [
|
|
93
117
|
Exclude<keyof Options, keyof TransformKeyOptions>
|
|
94
118
|
] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
|
|
95
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* `To` as a plain case literal, or `'camel'` (the default) for `undefined`.
|
|
121
|
+
*
|
|
122
|
+
* Not meant to be used directly. It is exported, like `PlainCase`, so that declaration
|
|
123
|
+
* files of code with a generic target case refer to it by name instead of writing out its
|
|
124
|
+
* definition.
|
|
125
|
+
*/
|
|
96
126
|
type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
|
|
97
127
|
/**
|
|
98
128
|
* The target case the runtime uses for one options object: `'camel'` when `targetCase`
|
|
@@ -176,7 +206,7 @@ declare const ACRONYM_PLURAL_SUFFIX = "s";
|
|
|
176
206
|
* (tail-recursive, so long keys compile).
|
|
177
207
|
*/
|
|
178
208
|
type HasOnlyKeyChars<S extends string> = S extends `${infer C}${infer Rest}` ? C extends KeyChar ? HasOnlyKeyChars<Rest> : false : true;
|
|
179
|
-
/** The canonical UUID form, where `x` stands for a hexadecimal digit (`
|
|
209
|
+
/** The canonical UUID form, where `x` stands for a hexadecimal digit (`isUuid`) */
|
|
180
210
|
type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
181
211
|
/**
|
|
182
212
|
* `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
|
|
@@ -185,14 +215,14 @@ type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
|
185
215
|
*/
|
|
186
216
|
type IsUuid<S extends string, Pattern extends string = UuidPattern> = Pattern extends `${infer P}${infer PatternRest}` ? S extends `${infer C}${infer Rest}` ? C extends (P extends 'x' ? HexDigit : P) ? IsUuid<Rest, PatternRest> : false : false : S extends '' ? true : false;
|
|
187
217
|
/**
|
|
188
|
-
* Keys the runtime leaves unchanged (`
|
|
218
|
+
* Keys the runtime leaves unchanged (`transformCase`): keys containing a
|
|
189
219
|
* character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
|
|
190
220
|
* a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
|
|
191
221
|
* keys that the other rules do not preserve are compared with the UUID form.
|
|
192
222
|
*/
|
|
193
223
|
type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
|
|
194
224
|
/**
|
|
195
|
-
* The case the runtime detects for a key that is not preserved (`
|
|
225
|
+
* The case the runtime detects for a key that is not preserved (`transformCase`), or
|
|
196
226
|
* `'none'` when it cannot be detected (the key is then left unchanged). A string is used
|
|
197
227
|
* rather than `null`, which every type accepts without `strictNullChecks`.
|
|
198
228
|
*/
|
|
@@ -216,14 +246,14 @@ type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends strin
|
|
|
216
246
|
* (`XAxis` -> `xAxis`)
|
|
217
247
|
*/
|
|
218
248
|
type PascalToCamelCase<S extends string, Prev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer Rest}` ? IsWordBoundary<C, Prev, Rest> extends true ? `${Acc}${S}` : PascalToCamelCase<Rest, C, `${Acc}${Lowercase<C>}`> : Acc;
|
|
219
|
-
/** snake_case/kebab-case to camelCase (`
|
|
249
|
+
/** snake_case/kebab-case to camelCase (`separatedToCamelCase`) */
|
|
220
250
|
type SeparatedToCamelCase<S extends string, Sep extends Separator, Acc extends string = ''> = S extends `${infer Word}${Sep}${infer Next}${infer Rest}` ? SeparatedToCamelCase<Rest, Sep, `${Acc}${Word}${Uppercase<Next>}`> : `${Acc}${S}`;
|
|
221
|
-
/** Replaces every `From` separator with `To` (`
|
|
251
|
+
/** Replaces every `From` separator with `To` (`replaceSeparator`) */
|
|
222
252
|
type ReplaceSeparator<S extends string, From extends Separator, To extends Separator, Acc extends string = ''> = S extends `${infer Head}${From}${infer Rest}` ? ReplaceSeparator<Rest, From, To, `${Acc}${Head}${To}`> : `${Acc}${S}`;
|
|
223
|
-
/** A single word in the target case (`
|
|
224
|
-
type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : Lowercase<K>;
|
|
225
|
-
/** A key of the detected case `From` in the target case (`
|
|
226
|
-
type FormatKey<K extends string, From extends StringCase, To extends StringCase> = From extends To ? K : From extends 'snake' | 'kebab' ? To extends 'camel' ? SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'> : To extends 'pascal' ? Capitalize<SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'>> : From extends 'snake' ? ReplaceSeparator<K, '_', '-'> : ReplaceSeparator<K, '-', '_'> : To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? PascalToCamelCase<K> : ToSeparatedCase<K, To extends 'snake' ? '_' : '-'>;
|
|
253
|
+
/** A single word in the target case (`singleWordToCase`) */
|
|
254
|
+
type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : To extends 'constant' ? Uppercase<K> : Lowercase<K>;
|
|
255
|
+
/** A key of the detected case `From` in the target case (the formatters `transformCase` picks) */
|
|
256
|
+
type FormatKey<K extends string, From extends StringCase, To extends StringCase> = From extends To ? K : From extends 'snake' | 'kebab' ? To extends 'camel' ? SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'> : To extends 'pascal' ? Capitalize<SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'>> : To extends 'constant' ? Uppercase<From extends 'snake' ? K : ReplaceSeparator<K, '-', '_'>> : From extends 'snake' ? ReplaceSeparator<K, '_', '-'> : ReplaceSeparator<K, '-', '_'> : To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? PascalToCamelCase<K> : To extends 'constant' ? Uppercase<ToSeparatedCase<K, '_'>> : ToSeparatedCase<K, To extends 'snake' ? '_' : '-'>;
|
|
227
257
|
/** Applies the detected case of `K` (`transformCase`) */
|
|
228
258
|
type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
|
|
229
259
|
/**
|
|
@@ -446,7 +476,7 @@ type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Op
|
|
|
446
476
|
* The key type `transformKey` returns for an options object. Each member of a union of
|
|
447
477
|
* options objects gives its own key, and an optional `targetCase` may also be omitted,
|
|
448
478
|
* which gives the camelCase key (see `TransformResultForOne`). Options typed `any`, or a
|
|
449
|
-
* `targetCase` typed `any`, give the key in any of the
|
|
479
|
+
* `targetCase` typed `any`, give the key in any of the target cases.
|
|
450
480
|
*
|
|
451
481
|
* Not exported: declaration files of code around `transformKey` refer to `TransformKey`
|
|
452
482
|
* instead, because options with a generic type do not match the overload that returns
|
|
@@ -501,7 +531,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
|
|
|
501
531
|
*
|
|
502
532
|
* @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
|
|
503
533
|
* and the result type is inferred, `depth` and `exclude` included. This form does not
|
|
504
|
-
* accept `depth` or `
|
|
534
|
+
* accept `depth`, `exclude` or `keepReferences`.
|
|
505
535
|
*/
|
|
506
536
|
declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
|
|
507
537
|
sourceCase: From;
|
|
@@ -519,17 +549,18 @@ declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sour
|
|
|
519
549
|
|
|
520
550
|
/**
|
|
521
551
|
* Transforms one key to camelCase, the way `transform` transforms every key. A string
|
|
522
|
-
* that is not a
|
|
523
|
-
* UUID, is returned unchanged (see "Key Preservation
|
|
552
|
+
* that is not a camelCase, PascalCase, snake_case or kebab-case key, such as
|
|
553
|
+
* `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
|
|
554
|
+
* Rules" in the readme).
|
|
524
555
|
*
|
|
525
556
|
* The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
|
|
526
557
|
*/
|
|
527
558
|
declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
|
|
528
559
|
/**
|
|
529
560
|
* Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
|
|
530
|
-
* every key. A string that is not a
|
|
531
|
-
* `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key
|
|
532
|
-
* Rules" in the readme).
|
|
561
|
+
* every key. A string that is not a camelCase, PascalCase, snake_case or kebab-case key,
|
|
562
|
+
* such as `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key
|
|
563
|
+
* Preservation Rules" in the readme).
|
|
533
564
|
*
|
|
534
565
|
* The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
|
|
535
566
|
* is typed `'user_id'`, a union of keys or target cases gives the union of the results,
|
|
@@ -552,4 +583,4 @@ declare function transformKey<K extends string, Options extends TransformKeyOpti
|
|
|
552
583
|
declare function transformKey(key: string, options?: TransformKeyOptions): string;
|
|
553
584
|
|
|
554
585
|
export { transform, transformKey };
|
|
555
|
-
export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
|
|
586
|
+
export type { CaseOrDefault, PlainCase, StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
|
package/dist/index.d.mts
CHANGED
|
@@ -15,23 +15,30 @@ type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
|
|
|
15
15
|
type IsStringLiteral<S extends string> = Record<never, never> extends Record<S, never> ? false : true;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Represents the supported string case formats for transformation
|
|
18
|
+
* Represents the supported string case formats for transformation. `'constant'` is
|
|
19
|
+
* CONSTANT_CASE (`USER_ID`), a target case only: keys such as `USER_ID` are left
|
|
20
|
+
* unchanged by every target case.
|
|
19
21
|
*/
|
|
20
|
-
type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
|
|
22
|
+
type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab' | 'constant';
|
|
21
23
|
/**
|
|
22
24
|
* The plain literal of a target case: a string enum member or a branded literal such as
|
|
23
25
|
* `'snake' & { __brand: 'Case' }` becomes `'snake'`, so it compares equal to the case
|
|
24
26
|
* detected from a key. Only literals are produced, never `To` itself: TypeScript 4.7
|
|
25
27
|
* narrows `To` to `To & StringCase` in a true branch, which is `never` for an enum member.
|
|
28
|
+
*
|
|
29
|
+
* Not meant to be used directly. It is exported so that declaration files of code with a
|
|
30
|
+
* generic target case refer to it instead of writing out the list of target cases, which
|
|
31
|
+
* would leave out cases added later.
|
|
26
32
|
*/
|
|
27
|
-
type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
|
|
33
|
+
type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : To extends 'constant' ? 'constant' : never;
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* Options for the transformKey function. The transform function takes them as well.
|
|
31
37
|
*/
|
|
32
38
|
interface TransformKeyOptions {
|
|
33
39
|
/**
|
|
34
|
-
* The target casing format to transform to
|
|
40
|
+
* The target casing format to transform to. `'constant'` is CONSTANT_CASE, such as
|
|
41
|
+
* `USER_ID`.
|
|
35
42
|
* @default 'camel'
|
|
36
43
|
*/
|
|
37
44
|
targetCase?: StringCase | undefined;
|
|
@@ -67,6 +74,23 @@ interface TransformOptions extends TransformKeyOptions {
|
|
|
67
74
|
* @default []
|
|
68
75
|
*/
|
|
69
76
|
exclude?: readonly string[] | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Whether an object reached through several references gives one result, as in the
|
|
79
|
+
* input: shared objects stay shared, and circular references point back into the
|
|
80
|
+
* result. Pass `false` for data that cannot contain such references, such as the
|
|
81
|
+
* output of `JSON.parse`: the transform then skips this bookkeeping, which makes inputs
|
|
82
|
+
* with many objects and arrays up to about twice as fast.
|
|
83
|
+
*
|
|
84
|
+
* Use it only for tree-shaped data. An object is then copied once for every path that
|
|
85
|
+
* leads to it, so shared objects inside shared objects multiply. Data with more than
|
|
86
|
+
* 256 nested objects, arrays, Maps and Sets on one path, as circular references always
|
|
87
|
+
* have, is converted again from the start with the bookkeeping, so it takes longer and
|
|
88
|
+
* getters can run twice. With a finite `depth`, circular references stop at the depth
|
|
89
|
+
* limit before that, and are copied once per path down to it.
|
|
90
|
+
*
|
|
91
|
+
* @default true
|
|
92
|
+
*/
|
|
93
|
+
keepReferences?: boolean | undefined;
|
|
70
94
|
/**
|
|
71
95
|
* @deprecated Has no effect. Every key's case is detected from the key itself, both at
|
|
72
96
|
* runtime and in the result type, so there is nothing to declare.
|
|
@@ -92,7 +116,13 @@ type NoUnknownOptions<Options, Known = TransformOptions> = {
|
|
|
92
116
|
type NoUnknownKeyOptions<Options> = [
|
|
93
117
|
Exclude<keyof Options, keyof TransformKeyOptions>
|
|
94
118
|
] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
|
|
95
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* `To` as a plain case literal, or `'camel'` (the default) for `undefined`.
|
|
121
|
+
*
|
|
122
|
+
* Not meant to be used directly. It is exported, like `PlainCase`, so that declaration
|
|
123
|
+
* files of code with a generic target case refer to it by name instead of writing out its
|
|
124
|
+
* definition.
|
|
125
|
+
*/
|
|
96
126
|
type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
|
|
97
127
|
/**
|
|
98
128
|
* The target case the runtime uses for one options object: `'camel'` when `targetCase`
|
|
@@ -176,7 +206,7 @@ declare const ACRONYM_PLURAL_SUFFIX = "s";
|
|
|
176
206
|
* (tail-recursive, so long keys compile).
|
|
177
207
|
*/
|
|
178
208
|
type HasOnlyKeyChars<S extends string> = S extends `${infer C}${infer Rest}` ? C extends KeyChar ? HasOnlyKeyChars<Rest> : false : true;
|
|
179
|
-
/** The canonical UUID form, where `x` stands for a hexadecimal digit (`
|
|
209
|
+
/** The canonical UUID form, where `x` stands for a hexadecimal digit (`isUuid`) */
|
|
180
210
|
type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
181
211
|
/**
|
|
182
212
|
* `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
|
|
@@ -185,14 +215,14 @@ type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
|
185
215
|
*/
|
|
186
216
|
type IsUuid<S extends string, Pattern extends string = UuidPattern> = Pattern extends `${infer P}${infer PatternRest}` ? S extends `${infer C}${infer Rest}` ? C extends (P extends 'x' ? HexDigit : P) ? IsUuid<Rest, PatternRest> : false : false : S extends '' ? true : false;
|
|
187
217
|
/**
|
|
188
|
-
* Keys the runtime leaves unchanged (`
|
|
218
|
+
* Keys the runtime leaves unchanged (`transformCase`): keys containing a
|
|
189
219
|
* character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
|
|
190
220
|
* a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
|
|
191
221
|
* keys that the other rules do not preserve are compared with the UUID form.
|
|
192
222
|
*/
|
|
193
223
|
type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
|
|
194
224
|
/**
|
|
195
|
-
* The case the runtime detects for a key that is not preserved (`
|
|
225
|
+
* The case the runtime detects for a key that is not preserved (`transformCase`), or
|
|
196
226
|
* `'none'` when it cannot be detected (the key is then left unchanged). A string is used
|
|
197
227
|
* rather than `null`, which every type accepts without `strictNullChecks`.
|
|
198
228
|
*/
|
|
@@ -216,14 +246,14 @@ type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends strin
|
|
|
216
246
|
* (`XAxis` -> `xAxis`)
|
|
217
247
|
*/
|
|
218
248
|
type PascalToCamelCase<S extends string, Prev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer Rest}` ? IsWordBoundary<C, Prev, Rest> extends true ? `${Acc}${S}` : PascalToCamelCase<Rest, C, `${Acc}${Lowercase<C>}`> : Acc;
|
|
219
|
-
/** snake_case/kebab-case to camelCase (`
|
|
249
|
+
/** snake_case/kebab-case to camelCase (`separatedToCamelCase`) */
|
|
220
250
|
type SeparatedToCamelCase<S extends string, Sep extends Separator, Acc extends string = ''> = S extends `${infer Word}${Sep}${infer Next}${infer Rest}` ? SeparatedToCamelCase<Rest, Sep, `${Acc}${Word}${Uppercase<Next>}`> : `${Acc}${S}`;
|
|
221
|
-
/** Replaces every `From` separator with `To` (`
|
|
251
|
+
/** Replaces every `From` separator with `To` (`replaceSeparator`) */
|
|
222
252
|
type ReplaceSeparator<S extends string, From extends Separator, To extends Separator, Acc extends string = ''> = S extends `${infer Head}${From}${infer Rest}` ? ReplaceSeparator<Rest, From, To, `${Acc}${Head}${To}`> : `${Acc}${S}`;
|
|
223
|
-
/** A single word in the target case (`
|
|
224
|
-
type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : Lowercase<K>;
|
|
225
|
-
/** A key of the detected case `From` in the target case (`
|
|
226
|
-
type FormatKey<K extends string, From extends StringCase, To extends StringCase> = From extends To ? K : From extends 'snake' | 'kebab' ? To extends 'camel' ? SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'> : To extends 'pascal' ? Capitalize<SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'>> : From extends 'snake' ? ReplaceSeparator<K, '_', '-'> : ReplaceSeparator<K, '-', '_'> : To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? PascalToCamelCase<K> : ToSeparatedCase<K, To extends 'snake' ? '_' : '-'>;
|
|
253
|
+
/** A single word in the target case (`singleWordToCase`) */
|
|
254
|
+
type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : To extends 'constant' ? Uppercase<K> : Lowercase<K>;
|
|
255
|
+
/** A key of the detected case `From` in the target case (the formatters `transformCase` picks) */
|
|
256
|
+
type FormatKey<K extends string, From extends StringCase, To extends StringCase> = From extends To ? K : From extends 'snake' | 'kebab' ? To extends 'camel' ? SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'> : To extends 'pascal' ? Capitalize<SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'>> : To extends 'constant' ? Uppercase<From extends 'snake' ? K : ReplaceSeparator<K, '-', '_'>> : From extends 'snake' ? ReplaceSeparator<K, '_', '-'> : ReplaceSeparator<K, '-', '_'> : To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? PascalToCamelCase<K> : To extends 'constant' ? Uppercase<ToSeparatedCase<K, '_'>> : ToSeparatedCase<K, To extends 'snake' ? '_' : '-'>;
|
|
227
257
|
/** Applies the detected case of `K` (`transformCase`) */
|
|
228
258
|
type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
|
|
229
259
|
/**
|
|
@@ -446,7 +476,7 @@ type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Op
|
|
|
446
476
|
* The key type `transformKey` returns for an options object. Each member of a union of
|
|
447
477
|
* options objects gives its own key, and an optional `targetCase` may also be omitted,
|
|
448
478
|
* which gives the camelCase key (see `TransformResultForOne`). Options typed `any`, or a
|
|
449
|
-
* `targetCase` typed `any`, give the key in any of the
|
|
479
|
+
* `targetCase` typed `any`, give the key in any of the target cases.
|
|
450
480
|
*
|
|
451
481
|
* Not exported: declaration files of code around `transformKey` refer to `TransformKey`
|
|
452
482
|
* instead, because options with a generic type do not match the overload that returns
|
|
@@ -501,7 +531,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
|
|
|
501
531
|
*
|
|
502
532
|
* @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
|
|
503
533
|
* and the result type is inferred, `depth` and `exclude` included. This form does not
|
|
504
|
-
* accept `depth` or `
|
|
534
|
+
* accept `depth`, `exclude` or `keepReferences`.
|
|
505
535
|
*/
|
|
506
536
|
declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
|
|
507
537
|
sourceCase: From;
|
|
@@ -519,17 +549,18 @@ declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sour
|
|
|
519
549
|
|
|
520
550
|
/**
|
|
521
551
|
* Transforms one key to camelCase, the way `transform` transforms every key. A string
|
|
522
|
-
* that is not a
|
|
523
|
-
* UUID, is returned unchanged (see "Key Preservation
|
|
552
|
+
* that is not a camelCase, PascalCase, snake_case or kebab-case key, such as
|
|
553
|
+
* `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
|
|
554
|
+
* Rules" in the readme).
|
|
524
555
|
*
|
|
525
556
|
* The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
|
|
526
557
|
*/
|
|
527
558
|
declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
|
|
528
559
|
/**
|
|
529
560
|
* Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
|
|
530
|
-
* every key. A string that is not a
|
|
531
|
-
* `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key
|
|
532
|
-
* Rules" in the readme).
|
|
561
|
+
* every key. A string that is not a camelCase, PascalCase, snake_case or kebab-case key,
|
|
562
|
+
* such as `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key
|
|
563
|
+
* Preservation Rules" in the readme).
|
|
533
564
|
*
|
|
534
565
|
* The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
|
|
535
566
|
* is typed `'user_id'`, a union of keys or target cases gives the union of the results,
|
|
@@ -552,4 +583,4 @@ declare function transformKey<K extends string, Options extends TransformKeyOpti
|
|
|
552
583
|
declare function transformKey(key: string, options?: TransformKeyOptions): string;
|
|
553
584
|
|
|
554
585
|
export { transform, transformKey };
|
|
555
|
-
export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
|
|
586
|
+
export type { CaseOrDefault, PlainCase, StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,23 +15,30 @@ type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
|
|
|
15
15
|
type IsStringLiteral<S extends string> = Record<never, never> extends Record<S, never> ? false : true;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Represents the supported string case formats for transformation
|
|
18
|
+
* Represents the supported string case formats for transformation. `'constant'` is
|
|
19
|
+
* CONSTANT_CASE (`USER_ID`), a target case only: keys such as `USER_ID` are left
|
|
20
|
+
* unchanged by every target case.
|
|
19
21
|
*/
|
|
20
|
-
type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab';
|
|
22
|
+
type StringCase = 'camel' | 'pascal' | 'snake' | 'kebab' | 'constant';
|
|
21
23
|
/**
|
|
22
24
|
* The plain literal of a target case: a string enum member or a branded literal such as
|
|
23
25
|
* `'snake' & { __brand: 'Case' }` becomes `'snake'`, so it compares equal to the case
|
|
24
26
|
* detected from a key. Only literals are produced, never `To` itself: TypeScript 4.7
|
|
25
27
|
* narrows `To` to `To & StringCase` in a true branch, which is `never` for an enum member.
|
|
28
|
+
*
|
|
29
|
+
* Not meant to be used directly. It is exported so that declaration files of code with a
|
|
30
|
+
* generic target case refer to it instead of writing out the list of target cases, which
|
|
31
|
+
* would leave out cases added later.
|
|
26
32
|
*/
|
|
27
|
-
type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : never;
|
|
33
|
+
type PlainCase<To> = To extends 'camel' ? 'camel' : To extends 'snake' ? 'snake' : To extends 'kebab' ? 'kebab' : To extends 'pascal' ? 'pascal' : To extends 'constant' ? 'constant' : never;
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* Options for the transformKey function. The transform function takes them as well.
|
|
31
37
|
*/
|
|
32
38
|
interface TransformKeyOptions {
|
|
33
39
|
/**
|
|
34
|
-
* The target casing format to transform to
|
|
40
|
+
* The target casing format to transform to. `'constant'` is CONSTANT_CASE, such as
|
|
41
|
+
* `USER_ID`.
|
|
35
42
|
* @default 'camel'
|
|
36
43
|
*/
|
|
37
44
|
targetCase?: StringCase | undefined;
|
|
@@ -67,6 +74,23 @@ interface TransformOptions extends TransformKeyOptions {
|
|
|
67
74
|
* @default []
|
|
68
75
|
*/
|
|
69
76
|
exclude?: readonly string[] | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Whether an object reached through several references gives one result, as in the
|
|
79
|
+
* input: shared objects stay shared, and circular references point back into the
|
|
80
|
+
* result. Pass `false` for data that cannot contain such references, such as the
|
|
81
|
+
* output of `JSON.parse`: the transform then skips this bookkeeping, which makes inputs
|
|
82
|
+
* with many objects and arrays up to about twice as fast.
|
|
83
|
+
*
|
|
84
|
+
* Use it only for tree-shaped data. An object is then copied once for every path that
|
|
85
|
+
* leads to it, so shared objects inside shared objects multiply. Data with more than
|
|
86
|
+
* 256 nested objects, arrays, Maps and Sets on one path, as circular references always
|
|
87
|
+
* have, is converted again from the start with the bookkeeping, so it takes longer and
|
|
88
|
+
* getters can run twice. With a finite `depth`, circular references stop at the depth
|
|
89
|
+
* limit before that, and are copied once per path down to it.
|
|
90
|
+
*
|
|
91
|
+
* @default true
|
|
92
|
+
*/
|
|
93
|
+
keepReferences?: boolean | undefined;
|
|
70
94
|
/**
|
|
71
95
|
* @deprecated Has no effect. Every key's case is detected from the key itself, both at
|
|
72
96
|
* runtime and in the result type, so there is nothing to declare.
|
|
@@ -92,7 +116,13 @@ type NoUnknownOptions<Options, Known = TransformOptions> = {
|
|
|
92
116
|
type NoUnknownKeyOptions<Options> = [
|
|
93
117
|
Exclude<keyof Options, keyof TransformKeyOptions>
|
|
94
118
|
] extends [never] ? unknown : NoUnknownOptions<Options, TransformKeyOptions>;
|
|
95
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* `To` as a plain case literal, or `'camel'` (the default) for `undefined`.
|
|
121
|
+
*
|
|
122
|
+
* Not meant to be used directly. It is exported, like `PlainCase`, so that declaration
|
|
123
|
+
* files of code with a generic target case refer to it by name instead of writing out its
|
|
124
|
+
* definition.
|
|
125
|
+
*/
|
|
96
126
|
type CaseOrDefault<To> = To extends undefined ? 'camel' : PlainCase<To>;
|
|
97
127
|
/**
|
|
98
128
|
* The target case the runtime uses for one options object: `'camel'` when `targetCase`
|
|
@@ -176,7 +206,7 @@ declare const ACRONYM_PLURAL_SUFFIX = "s";
|
|
|
176
206
|
* (tail-recursive, so long keys compile).
|
|
177
207
|
*/
|
|
178
208
|
type HasOnlyKeyChars<S extends string> = S extends `${infer C}${infer Rest}` ? C extends KeyChar ? HasOnlyKeyChars<Rest> : false : true;
|
|
179
|
-
/** The canonical UUID form, where `x` stands for a hexadecimal digit (`
|
|
209
|
+
/** The canonical UUID form, where `x` stands for a hexadecimal digit (`isUuid`) */
|
|
180
210
|
type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
181
211
|
/**
|
|
182
212
|
* `true` when `S` is a UUID in canonical form. `S` is compared with `UuidPattern` one
|
|
@@ -185,14 +215,14 @@ type UuidPattern = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
|
|
185
215
|
*/
|
|
186
216
|
type IsUuid<S extends string, Pattern extends string = UuidPattern> = Pattern extends `${infer P}${infer PatternRest}` ? S extends `${infer C}${infer Rest}` ? C extends (P extends 'x' ? HexDigit : P) ? IsUuid<Rest, PatternRest> : false : false : S extends '' ? true : false;
|
|
187
217
|
/**
|
|
188
|
-
* Keys the runtime leaves unchanged (`
|
|
218
|
+
* Keys the runtime leaves unchanged (`transformCase`): keys containing a
|
|
189
219
|
* character outside `A-Z a-z 0-9 _ -`, keys not starting with a letter, keys ending with
|
|
190
220
|
* a separator, and UUIDs. A UUID that starts with a digit is already preserved, so only
|
|
191
221
|
* keys that the other rules do not preserve are compared with the UUID form.
|
|
192
222
|
*/
|
|
193
223
|
type IsPreservedKey<K extends string> = HasOnlyKeyChars<K> extends false ? true : K extends `${LowerLetter | UpperLetter}${string}` ? K extends `${string}${Separator}` ? true : IsUuid<K> : true;
|
|
194
224
|
/**
|
|
195
|
-
* The case the runtime detects for a key that is not preserved (`
|
|
225
|
+
* The case the runtime detects for a key that is not preserved (`transformCase`), or
|
|
196
226
|
* `'none'` when it cannot be detected (the key is then left unchanged). A string is used
|
|
197
227
|
* rather than `null`, which every type accepts without `strictNullChecks`.
|
|
198
228
|
*/
|
|
@@ -216,14 +246,14 @@ type ToSeparatedCase<S extends string, Sep extends Separator, Prev extends strin
|
|
|
216
246
|
* (`XAxis` -> `xAxis`)
|
|
217
247
|
*/
|
|
218
248
|
type PascalToCamelCase<S extends string, Prev extends string = '', Acc extends string = ''> = S extends `${infer C}${infer Rest}` ? IsWordBoundary<C, Prev, Rest> extends true ? `${Acc}${S}` : PascalToCamelCase<Rest, C, `${Acc}${Lowercase<C>}`> : Acc;
|
|
219
|
-
/** snake_case/kebab-case to camelCase (`
|
|
249
|
+
/** snake_case/kebab-case to camelCase (`separatedToCamelCase`) */
|
|
220
250
|
type SeparatedToCamelCase<S extends string, Sep extends Separator, Acc extends string = ''> = S extends `${infer Word}${Sep}${infer Next}${infer Rest}` ? SeparatedToCamelCase<Rest, Sep, `${Acc}${Word}${Uppercase<Next>}`> : `${Acc}${S}`;
|
|
221
|
-
/** Replaces every `From` separator with `To` (`
|
|
251
|
+
/** Replaces every `From` separator with `To` (`replaceSeparator`) */
|
|
222
252
|
type ReplaceSeparator<S extends string, From extends Separator, To extends Separator, Acc extends string = ''> = S extends `${infer Head}${From}${infer Rest}` ? ReplaceSeparator<Rest, From, To, `${Acc}${Head}${To}`> : `${Acc}${S}`;
|
|
223
|
-
/** A single word in the target case (`
|
|
224
|
-
type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : Lowercase<K>;
|
|
225
|
-
/** A key of the detected case `From` in the target case (`
|
|
226
|
-
type FormatKey<K extends string, From extends StringCase, To extends StringCase> = From extends To ? K : From extends 'snake' | 'kebab' ? To extends 'camel' ? SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'> : To extends 'pascal' ? Capitalize<SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'>> : From extends 'snake' ? ReplaceSeparator<K, '_', '-'> : ReplaceSeparator<K, '-', '_'> : To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? PascalToCamelCase<K> : ToSeparatedCase<K, To extends 'snake' ? '_' : '-'>;
|
|
253
|
+
/** A single word in the target case (`singleWordToCase`) */
|
|
254
|
+
type SingleWordToCase<K extends string, To extends StringCase> = To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? Uncapitalize<K> : To extends 'constant' ? Uppercase<K> : Lowercase<K>;
|
|
255
|
+
/** A key of the detected case `From` in the target case (the formatters `transformCase` picks) */
|
|
256
|
+
type FormatKey<K extends string, From extends StringCase, To extends StringCase> = From extends To ? K : From extends 'snake' | 'kebab' ? To extends 'camel' ? SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'> : To extends 'pascal' ? Capitalize<SeparatedToCamelCase<K, From extends 'snake' ? '_' : '-'>> : To extends 'constant' ? Uppercase<From extends 'snake' ? K : ReplaceSeparator<K, '-', '_'>> : From extends 'snake' ? ReplaceSeparator<K, '_', '-'> : ReplaceSeparator<K, '-', '_'> : To extends 'pascal' ? Capitalize<K> : To extends 'camel' ? PascalToCamelCase<K> : To extends 'constant' ? Uppercase<ToSeparatedCase<K, '_'>> : ToSeparatedCase<K, To extends 'snake' ? '_' : '-'>;
|
|
227
257
|
/** Applies the detected case of `K` (`transformCase`) */
|
|
228
258
|
type TransformDetectedKey<K extends string, Detected, To extends StringCase> = Detected extends 'single' ? SingleWordToCase<K, To> : Detected extends StringCase ? FormatKey<K, Detected, To> : K;
|
|
229
259
|
/**
|
|
@@ -446,7 +476,7 @@ type TransformResultFor<T, Options> = IsAny<Options> extends true ? unknown : Op
|
|
|
446
476
|
* The key type `transformKey` returns for an options object. Each member of a union of
|
|
447
477
|
* options objects gives its own key, and an optional `targetCase` may also be omitted,
|
|
448
478
|
* which gives the camelCase key (see `TransformResultForOne`). Options typed `any`, or a
|
|
449
|
-
* `targetCase` typed `any`, give the key in any of the
|
|
479
|
+
* `targetCase` typed `any`, give the key in any of the target cases.
|
|
450
480
|
*
|
|
451
481
|
* Not exported: declaration files of code around `transformKey` refer to `TransformKey`
|
|
452
482
|
* instead, because options with a generic type do not match the overload that returns
|
|
@@ -501,7 +531,7 @@ declare function transform<T, To extends StringCase | undefined = StringCase | u
|
|
|
501
531
|
*
|
|
502
532
|
* @deprecated `sourceCase` has no effect: leave out the type arguments and `sourceCase`,
|
|
503
533
|
* and the result type is inferred, `depth` and `exclude` included. This form does not
|
|
504
|
-
* accept `depth` or `
|
|
534
|
+
* accept `depth`, `exclude` or `keepReferences`.
|
|
505
535
|
*/
|
|
506
536
|
declare function transform<T, From extends StringCase, To extends StringCase>(data: T, options: {
|
|
507
537
|
sourceCase: From;
|
|
@@ -519,17 +549,18 @@ declare function transform<T, K>(data: T, options?: Omit<TransformOptions, 'sour
|
|
|
519
549
|
|
|
520
550
|
/**
|
|
521
551
|
* Transforms one key to camelCase, the way `transform` transforms every key. A string
|
|
522
|
-
* that is not a
|
|
523
|
-
* UUID, is returned unchanged (see "Key Preservation
|
|
552
|
+
* that is not a camelCase, PascalCase, snake_case or kebab-case key, such as
|
|
553
|
+
* `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key Preservation
|
|
554
|
+
* Rules" in the readme).
|
|
524
555
|
*
|
|
525
556
|
* The result type is the transformed key: `transformKey('user_id')` is typed `'userId'`.
|
|
526
557
|
*/
|
|
527
558
|
declare function transformKey<K extends string>(key: K, options?: undefined): TransformKey<K>;
|
|
528
559
|
/**
|
|
529
560
|
* Transforms one key to `targetCase` (default `'camel'`), the way `transform` transforms
|
|
530
|
-
* every key. A string that is not a
|
|
531
|
-
* `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key
|
|
532
|
-
* Rules" in the readme).
|
|
561
|
+
* every key. A string that is not a camelCase, PascalCase, snake_case or kebab-case key,
|
|
562
|
+
* such as `'Hello World'`, `'USER_ID'` or a UUID, is returned unchanged (see "Key
|
|
563
|
+
* Preservation Rules" in the readme).
|
|
533
564
|
*
|
|
534
565
|
* The result type is the transformed key: `transformKey('userID', { targetCase: 'snake' })`
|
|
535
566
|
* is typed `'user_id'`, a union of keys or target cases gives the union of the results,
|
|
@@ -552,4 +583,4 @@ declare function transformKey<K extends string, Options extends TransformKeyOpti
|
|
|
552
583
|
declare function transformKey(key: string, options?: TransformKeyOptions): string;
|
|
553
584
|
|
|
554
585
|
export { transform, transformKey };
|
|
555
|
-
export type { StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
|
|
586
|
+
export type { CaseOrDefault, PlainCase, StringCase, TransformKey, TransformKeyOptions, TransformKeyToEach, TransformOptions, TransformResult, TransformResultFor, TransformResultForExcluding, TransformValue, TransformValueExcluding };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const e=/^[
|
|
1
|
+
"use strict";const e=/^[^.]+(?:\.[^.]+)*$/;function t(e){if("string"==typeof e)return JSON.stringify(e);if("bigint"==typeof e)return`${e}n`;if(null===e)return"null";if("object"==typeof e||"function"==typeof e)try{return Object.prototype.toString.call(e)}catch(e){return"an object"}return String(e)}function n(e){const t=e.indexOf(".");return-1===t?[e]:[e.slice(0,t),e.slice(t+1)]}const r=[];function o(e){let t,n=!1;for(const r of e)void 0===r?n=!0:(null!=t?t:t=[]).push(r);return{keepKey:n,pathsBelow:t?s(t):r,scope:void 0}}function s(e){return 1===e.length?e:[...new Set(e)].sort()}function c(e,t){const r=[];let s;for(const t of e){const[e,o]=n(t);if("*"===e)r.push(o);else{null!=s||(s=new Map);const t=s.get(e);t?t.push(o):s.set(e,[o])}}const c=new Map;for(const[e,t]of null!=s?s:[])c.set(e,o(r.length?[...t,...r]:t));const{targetCase:u,depth:i,keepReferences:a}=t.plain.options,f={targetCase:u,depth:i,keepReferences:a,exclude:null},l={options:f,visitedRefs:[],named:c,other:o(r),root:t};return f.exclude=l,l}function u(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function i(e,t){var n;return null!==(n=t.scope)&&void 0!==n?n:t.scope=0===t.pathsBelow.length?e.root.plain:function(e,t){const n=JSON.stringify(e);let r=t.states.get(n);return r||(r=c(e,t),t.states.set(n,r)),r}(t.pathsBelow,e.root)}const a="[object Object]";function f(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}const l={};let p=!1;function d(e,t,n,r){let o=e[t];o.size>=16e6&&(p=!0,o=e[t]=new Map([[l,o]])),o.set(n,r)}function h(e){return e>=48&&e<=57}function y(e){return e>=97&&e<=122}function g(e){return e>=65&&e<=90}const w="s".charCodeAt(0);function v(e,t){if(!g(e.charCodeAt(t)))return!1;const n=e.charCodeAt(t-1);if(y(n)||h(n))return!0;const r=e.charCodeAt(t+1);return!(!g(n)||!y(r))&&(r!==w||y(e.charCodeAt(t+2)))}function C(e,t,n){return e.split(t).join(n)}function b(e,t,n){let r=n?e[0].toUpperCase():e[0],o=1,s=e.indexOf(t,1);for(;-1!==s;)r+=e.slice(o,s)+e[s+1].toUpperCase(),o=s+2,s=e.indexOf(t,o);return r+e.slice(o)}function U(e,t,n){let r="",o=0;for(let n=1;n<e.length;n++)v(e,n)&&(r+=e.slice(o,n)+t,o=n);return r+=e.slice(o),n?r.toUpperCase():r.toLowerCase()}function m(e,t){const n=e.length;if(0===n)return e;let r=e.charCodeAt(0);const o=g(r);if(!o&&!y(r))return e;let s=o?1:0,c=!o,u=0,i=0,a=!1,f=r;for(let t=1;t<n;t++){if(r=e.charCodeAt(t),y(r))c=!0;else if(g(r))s++;else if(95===r)u++,95===f&&(a=!0);else if(45===r)i++,45===f&&(a=!0);else if(!h(r))return e;f=r}if(95===f||45===f)return e;if(0===u&&0===i){if(0===s||1===s&&o)return function(e,t){const n=g(e.charCodeAt(0));switch(t){case"pascal":return n?e:e[0].toUpperCase()+e.slice(1);case"camel":return n?e[0].toLowerCase()+e.slice(1):e;case"constant":return e.toUpperCase();default:return n?e.toLowerCase():e}}(e,t);if(!o)switch(t){case"snake":return U(e,"_",!1);case"kebab":return U(e,"-",!1);case"pascal":return e[0].toUpperCase()+e.slice(1);case"constant":return U(e,"_",!0);default:return e}if(!c)return e;switch(t){case"snake":return U(e,"_",!1);case"kebab":return U(e,"-",!1);case"camel":return function(e){let t=1;for(;t<e.length&&!v(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)}(e);case"constant":return U(e,"_",!0);default:return e}}if(s>0||a||u>0&&i>0)return e;if(u>0)switch(t){case"camel":return b(e,"_",!1);case"pascal":return b(e,"_",!0);case"kebab":return C(e,"_","-");case"constant":return e.toUpperCase();default:return e}if(function(e){if(36!==e.length)return!1;for(let t=0;t<36;t++){const n=e.charCodeAt(t);if(8===t||13===t||18===t||23===t){if(45!==n)return!1}else if(!(h(n)||n>=97&&n<=102||n>=65&&n<=70))return!1}return!0}(e))return e;switch(t){case"camel":return b(e,"-",!1);case"pascal":return b(e,"-",!0);case"snake":return C(e,"-","_");case"constant":return C(e,"-","_").toUpperCase();default:return e}}const k=new Map;let j,x={current:new Map,previous:new Map},M=0;function O(e,t){if(e.length>100)return m(e,t);if(t!==j){let e=k.get(t);e||(e={current:new Map,previous:new Map},k.set(t,e)),j=t,x=e}const n=x;let r=n.current.get(e);if(void 0!==r)return M=0,r;if(r=n.previous.get(e),void 0===r){if(r=m(e,t),++M>256&&M%8!=0)return r}else M=0;return n.current.size>=1e3&&(n.previous=n.current,n.current=new Map),n.current.set(e,r),r}const A={reason:"nesting limit reached"};function _(e,t,n=[],r=0,o=0){var s;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const c=t.depth===1/0?0:r,h=t.keepReferences;if(h){const t=function(e,t){const n=e.get(t);if(void 0!==n||!p)return n;let r=e.get(l);for(;void 0!==r;){const e=r.get(t);if(void 0!==e)return e;r=r.get(l)}}(null!==(s=n[c])&&void 0!==s?s:n[c]=new Map,e);if(void 0!==t)return t}else if(o>=256)throw A;if(Array.isArray(e)){const s=[];return h&&d(n,c,e,s),function(e,t,n,r,o,s){for(const c of e)t.push(null!==c&&"object"==typeof c?_(c,n,r,o,s+1):c)}(e,s,t,n,r,o),s}const y=r+1,g=function(e){try{return Object.prototype.toString.call(e)}catch(e){return a}}(e);if(function(e,t){if(!(e instanceof Map)&&"[object Map]"!==t)return!1;try{return Map.prototype.has.call(e,void 0),!0}catch(e){return!1}}(e,g)){const r=new Map;return h&&d(n,c,e,r),null===t.exclude?function(e,t,n,r,o,s){for(const[c,u]of e)t.set("string"==typeof c?m(c,n.targetCase):c,_(u,n,r,o,s+1))}(e,r,t,n,y,o):function(e,t,n,r,o){for(const[s,c]of e){const e=u(n,s),a=i(n,e);t.set("string"!=typeof s||e.keepKey?s:m(s,n.options.targetCase),_(c,a.options,a.visitedRefs,r,o+1))}}(e,r,t.exclude,y,o),r}if(function(e,t){if(!(e instanceof Set)&&"[object Set]"!==t)return!1;try{return Set.prototype.has.call(e,void 0),!0}catch(e){return!1}}(e,g)){const r=new Set;return h&&d(n,c,e,r),function(e,t,n,r,o,s){for(const c of e)t.add(_(c,n,r,o,s+1))}(e,r,t,n,y,o),r}if(g===a){const r=e,s={};if(h&&d(n,c,e,s),null===t.exclude)for(const e in r)Object.prototype.hasOwnProperty.call(r,e)&&f(s,O(e,t.targetCase),_(r[e],t,n,y,o+1));else!function(e,t,n,r,o){for(const s in e)if(Object.prototype.hasOwnProperty.call(e,s)){const c=u(n,s),a=i(n,c);f(t,c.keepKey?s:O(s,n.options.targetCase),_(e[s],a.options,a.visitedRefs,r,o+1))}}(r,s,t.exclude,y,o);for(const e of Object.getOwnPropertySymbols(r))s[e]=r[e];return s}return e}const R=["camel","snake","pascal","kebab","constant"];function S(e){const n=e||"camel";if(r=n,!R.includes(r))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${R.join(", ")}.`);var r;return n}function $(n,r,o){const u=void 0===o?null:function(n,r){if(null==n)return null;if(!Array.isArray(n))throw new TypeError(`Unsupported exclude ${t(n)}. Use an array of paths, such as ["metadata.*"].`);for(const r of n)if("string"!=typeof r||!e.test(r))throw new TypeError(`Unsupported exclude path ${t(r)}. Use non-empty keys joined with dots, such as "metadata.*".`);if(0===n.length)return null;const o={plain:{options:r,visitedRefs:[]},states:new Map};return c(s(n),o)}(o,r);return null===u?_(n,r):_(n,u.options,u.visitedRefs)}exports.transform=function(e,n){var r,o;const s=null==n?void 0:n.targetCase,c=null!==(r=null==n?void 0:n.depth)&&void 0!==r?r:1/0,u=S(s);if("number"!=typeof c||Number.isNaN(c))throw new TypeError(`Unsupported depth ${t(c)}. Use a number, or omit depth to transform every level.`);const i=null==n?void 0:n.exclude,a=null===(o=null==n?void 0:n.keepReferences)||void 0===o||o;if("boolean"!=typeof a)throw new TypeError(`Unsupported keepReferences ${t(a)}. Use true or false.`);if(!a)try{return $(e,{targetCase:u,depth:c,keepReferences:a,exclude:null},i)}catch(e){if(e!==A)throw e}return $(e,{targetCase:u,depth:c,keepReferences:!0,exclude:null},i)},exports.transformKey=function(e,n){if("string"!=typeof e)throw new TypeError(`Unsupported key ${t(e)}. Use a string.`);if("string"==typeof n)throw new TypeError(`Unsupported options ${t(n)}. Use an options object: { targetCase: ${t(n)} }.`);return m(e,S(null==n?void 0:n.targetCase))};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=/^[
|
|
1
|
+
const e=/^[^.]+(?:\.[^.]+)*$/;function t(e){if("string"==typeof e)return JSON.stringify(e);if("bigint"==typeof e)return`${e}n`;if(null===e)return"null";if("object"==typeof e||"function"==typeof e)try{return Object.prototype.toString.call(e)}catch(e){return"an object"}return String(e)}function n(e){const t=e.indexOf(".");return-1===t?[e]:[e.slice(0,t),e.slice(t+1)]}const r=[];function o(e){let t,n=!1;for(const r of e)void 0===r?n=!0:(null!=t?t:t=[]).push(r);return{keepKey:n,pathsBelow:t?s(t):r,scope:void 0}}function s(e){return 1===e.length?e:[...new Set(e)].sort()}function c(e,t){const r=[];let s;for(const t of e){const[e,o]=n(t);if("*"===e)r.push(o);else{null!=s||(s=new Map);const t=s.get(e);t?t.push(o):s.set(e,[o])}}const c=new Map;for(const[e,t]of null!=s?s:[])c.set(e,o(r.length?[...t,...r]:t));const{targetCase:u,depth:i,keepReferences:a}=t.plain.options,f={targetCase:u,depth:i,keepReferences:a,exclude:null},l={options:f,visitedRefs:[],named:c,other:o(r),root:t};return f.exclude=l,l}function u(e,t){const n="string"==typeof t?e.named.get(t):void 0;return null!=n?n:e.other}function i(e,t){var n;return null!==(n=t.scope)&&void 0!==n?n:t.scope=0===t.pathsBelow.length?e.root.plain:function(e,t){const n=JSON.stringify(e);let r=t.states.get(n);return r||(r=c(e,t),t.states.set(n,r)),r}(t.pathsBelow,e.root)}const a="[object Object]";function f(e,t,n){"__proto__"===t?Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0}):e[t]=n}const l={};let p=!1;function d(e,t,n,r){let o=e[t];o.size>=16e6&&(p=!0,o=e[t]=new Map([[l,o]])),o.set(n,r)}function h(e){return e>=48&&e<=57}function y(e){return e>=97&&e<=122}function g(e){return e>=65&&e<=90}const w="s".charCodeAt(0);function v(e,t){if(!g(e.charCodeAt(t)))return!1;const n=e.charCodeAt(t-1);if(y(n)||h(n))return!0;const r=e.charCodeAt(t+1);return!(!g(n)||!y(r))&&(r!==w||y(e.charCodeAt(t+2)))}function C(e,t,n){return e.split(t).join(n)}function b(e,t,n){let r=n?e[0].toUpperCase():e[0],o=1,s=e.indexOf(t,1);for(;-1!==s;)r+=e.slice(o,s)+e[s+1].toUpperCase(),o=s+2,s=e.indexOf(t,o);return r+e.slice(o)}function U(e,t,n){let r="",o=0;for(let n=1;n<e.length;n++)v(e,n)&&(r+=e.slice(o,n)+t,o=n);return r+=e.slice(o),n?r.toUpperCase():r.toLowerCase()}function k(e,t){const n=e.length;if(0===n)return e;let r=e.charCodeAt(0);const o=g(r);if(!o&&!y(r))return e;let s=o?1:0,c=!o,u=0,i=0,a=!1,f=r;for(let t=1;t<n;t++){if(r=e.charCodeAt(t),y(r))c=!0;else if(g(r))s++;else if(95===r)u++,95===f&&(a=!0);else if(45===r)i++,45===f&&(a=!0);else if(!h(r))return e;f=r}if(95===f||45===f)return e;if(0===u&&0===i){if(0===s||1===s&&o)return function(e,t){const n=g(e.charCodeAt(0));switch(t){case"pascal":return n?e:e[0].toUpperCase()+e.slice(1);case"camel":return n?e[0].toLowerCase()+e.slice(1):e;case"constant":return e.toUpperCase();default:return n?e.toLowerCase():e}}(e,t);if(!o)switch(t){case"snake":return U(e,"_",!1);case"kebab":return U(e,"-",!1);case"pascal":return e[0].toUpperCase()+e.slice(1);case"constant":return U(e,"_",!0);default:return e}if(!c)return e;switch(t){case"snake":return U(e,"_",!1);case"kebab":return U(e,"-",!1);case"camel":return function(e){let t=1;for(;t<e.length&&!v(e,t);)t++;return e.slice(0,t).toLowerCase()+e.slice(t)}(e);case"constant":return U(e,"_",!0);default:return e}}if(s>0||a||u>0&&i>0)return e;if(u>0)switch(t){case"camel":return b(e,"_",!1);case"pascal":return b(e,"_",!0);case"kebab":return C(e,"_","-");case"constant":return e.toUpperCase();default:return e}if(function(e){if(36!==e.length)return!1;for(let t=0;t<36;t++){const n=e.charCodeAt(t);if(8===t||13===t||18===t||23===t){if(45!==n)return!1}else if(!(h(n)||n>=97&&n<=102||n>=65&&n<=70))return!1}return!0}(e))return e;switch(t){case"camel":return b(e,"-",!1);case"pascal":return b(e,"-",!0);case"snake":return C(e,"-","_");case"constant":return C(e,"-","_").toUpperCase();default:return e}}const m=new Map;let j,x={current:new Map,previous:new Map},M=0;function O(e,t){if(e.length>100)return k(e,t);if(t!==j){let e=m.get(t);e||(e={current:new Map,previous:new Map},m.set(t,e)),j=t,x=e}const n=x;let r=n.current.get(e);if(void 0!==r)return M=0,r;if(r=n.previous.get(e),void 0===r){if(r=k(e,t),++M>256&&M%8!=0)return r}else M=0;return n.current.size>=1e3&&(n.previous=n.current,n.current=new Map),n.current.set(e,r),r}const A={reason:"nesting limit reached"};function _(e,t,n=[],r=0,o=0){var s;if(null===e||"object"!=typeof e)return e;if(r>=t.depth)return e;const c=t.depth===1/0?0:r,h=t.keepReferences;if(h){const t=function(e,t){const n=e.get(t);if(void 0!==n||!p)return n;let r=e.get(l);for(;void 0!==r;){const e=r.get(t);if(void 0!==e)return e;r=r.get(l)}}(null!==(s=n[c])&&void 0!==s?s:n[c]=new Map,e);if(void 0!==t)return t}else if(o>=256)throw A;if(Array.isArray(e)){const s=[];return h&&d(n,c,e,s),function(e,t,n,r,o,s){for(const c of e)t.push(null!==c&&"object"==typeof c?_(c,n,r,o,s+1):c)}(e,s,t,n,r,o),s}const y=r+1,g=function(e){try{return Object.prototype.toString.call(e)}catch(e){return a}}(e);if(function(e,t){if(!(e instanceof Map)&&"[object Map]"!==t)return!1;try{return Map.prototype.has.call(e,void 0),!0}catch(e){return!1}}(e,g)){const r=new Map;return h&&d(n,c,e,r),null===t.exclude?function(e,t,n,r,o,s){for(const[c,u]of e)t.set("string"==typeof c?k(c,n.targetCase):c,_(u,n,r,o,s+1))}(e,r,t,n,y,o):function(e,t,n,r,o){for(const[s,c]of e){const e=u(n,s),a=i(n,e);t.set("string"!=typeof s||e.keepKey?s:k(s,n.options.targetCase),_(c,a.options,a.visitedRefs,r,o+1))}}(e,r,t.exclude,y,o),r}if(function(e,t){if(!(e instanceof Set)&&"[object Set]"!==t)return!1;try{return Set.prototype.has.call(e,void 0),!0}catch(e){return!1}}(e,g)){const r=new Set;return h&&d(n,c,e,r),function(e,t,n,r,o,s){for(const c of e)t.add(_(c,n,r,o,s+1))}(e,r,t,n,y,o),r}if(g===a){const r=e,s={};if(h&&d(n,c,e,s),null===t.exclude)for(const e in r)Object.prototype.hasOwnProperty.call(r,e)&&f(s,O(e,t.targetCase),_(r[e],t,n,y,o+1));else!function(e,t,n,r,o){for(const s in e)if(Object.prototype.hasOwnProperty.call(e,s)){const c=u(n,s),a=i(n,c);f(t,c.keepKey?s:O(s,n.options.targetCase),_(e[s],a.options,a.visitedRefs,r,o+1))}}(r,s,t.exclude,y,o);for(const e of Object.getOwnPropertySymbols(r))s[e]=r[e];return s}return e}const R=["camel","snake","pascal","kebab","constant"];function S(e){const n=e||"camel";if(r=n,!R.includes(r))throw new TypeError(`Unsupported targetCase ${t(n)}. Use one of: ${R.join(", ")}.`);var r;return n}function $(e,n){var r,o;const s=null==n?void 0:n.targetCase,c=null!==(r=null==n?void 0:n.depth)&&void 0!==r?r:1/0,u=S(s);if("number"!=typeof c||Number.isNaN(c))throw new TypeError(`Unsupported depth ${t(c)}. Use a number, or omit depth to transform every level.`);const i=null==n?void 0:n.exclude,a=null===(o=null==n?void 0:n.keepReferences)||void 0===o||o;if("boolean"!=typeof a)throw new TypeError(`Unsupported keepReferences ${t(a)}. Use true or false.`);if(!a)try{return E(e,{targetCase:u,depth:c,keepReferences:a,exclude:null},i)}catch(e){if(e!==A)throw e}return E(e,{targetCase:u,depth:c,keepReferences:!0,exclude:null},i)}function E(n,r,o){const u=void 0===o?null:function(n,r){if(null==n)return null;if(!Array.isArray(n))throw new TypeError(`Unsupported exclude ${t(n)}. Use an array of paths, such as ["metadata.*"].`);for(const r of n)if("string"!=typeof r||!e.test(r))throw new TypeError(`Unsupported exclude path ${t(r)}. Use non-empty keys joined with dots, such as "metadata.*".`);if(0===n.length)return null;const o={plain:{options:r,visitedRefs:[]},states:new Map};return c(s(n),o)}(o,r);return null===u?_(n,r):_(n,u.options,u.visitedRefs)}function T(e,n){if("string"!=typeof e)throw new TypeError(`Unsupported key ${t(e)}. Use a string.`);if("string"==typeof n)throw new TypeError(`Unsupported options ${t(n)}. Use an options object: { targetCase: ${t(n)} }.`);return k(e,S(null==n?void 0:n.targetCase))}export{$ as transform,T as transformKey};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deep-case-crafter",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Convert object keys between camelCase, snake_case, kebab-case and PascalCase, deeply (objects, arrays, Map, Set), with exact TypeScript types",
|
|
3
|
+
"version": "3.4.0",
|
|
4
|
+
"description": "Convert object keys between camelCase, snake_case, kebab-case and PascalCase, or to CONSTANT_CASE, deeply (objects, arrays, Map, Set), with exact TypeScript types",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -36,12 +36,14 @@
|
|
|
36
36
|
"snake_case",
|
|
37
37
|
"kebab-case",
|
|
38
38
|
"PascalCase",
|
|
39
|
+
"CONSTANT_CASE",
|
|
39
40
|
"camelcase",
|
|
40
41
|
"snakecase",
|
|
41
42
|
"kebabcase",
|
|
42
43
|
"pascalcase",
|
|
43
44
|
"camel-case",
|
|
44
45
|
"snake-case",
|
|
46
|
+
"constant-case",
|
|
45
47
|
"case-convert",
|
|
46
48
|
"convert-case",
|
|
47
49
|
"case-conversion",
|
package/readme.md
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
[](https://github.com/gyrgy/deep-case-crafter/blob/main/LICENSE)
|
|
10
10
|
|
|
11
11
|
Converts the keys of nested objects, arrays, Maps and Sets between camelCase,
|
|
12
|
-
snake_case, kebab-case and PascalCase, and types the result
|
|
12
|
+
snake_case, kebab-case and PascalCase, or to CONSTANT_CASE, and types the result
|
|
13
|
+
exactly.
|
|
13
14
|
|
|
14
15
|
```typescript
|
|
15
16
|
import { transform } from 'deep-case-crafter';
|
|
@@ -34,6 +35,9 @@ const body = transform(user, { targetCase: 'snake' });
|
|
|
34
35
|
keys in different cases can be mixed in one input.
|
|
35
36
|
- **Acronym-aware**: `userID` → `user_id`, `HTTPResponse` → `http_response` or
|
|
36
37
|
`httpResponse`.
|
|
38
|
+
- **CONSTANT_CASE output**: `targetCase: 'constant'` turns `userID` into `USER_ID` and
|
|
39
|
+
`HTTPResponse` into `HTTP_RESPONSE`. It is an output case only: the other cases leave
|
|
40
|
+
`USER_ID` as it is.
|
|
37
41
|
- **Deep**: objects, arrays, `Map` and `Set`, down to an optional `depth`, circular
|
|
38
42
|
references included. `Date`, `RegExp`, typed arrays and other built-in values are
|
|
39
43
|
returned untouched.
|
|
@@ -89,6 +93,10 @@ console.log(pascalCaseResult);
|
|
|
89
93
|
const kebabCaseResult = transform(input, { targetCase: 'kebab' });
|
|
90
94
|
console.log(kebabCaseResult);
|
|
91
95
|
// Output: { "user-id": 1, "first-name": "John" }
|
|
96
|
+
|
|
97
|
+
const constantCaseResult = transform(input, { targetCase: 'constant' });
|
|
98
|
+
console.log(constantCaseResult);
|
|
99
|
+
// Output: { USER_ID: 1, FIRST_NAME: 'John' }
|
|
92
100
|
```
|
|
93
101
|
|
|
94
102
|
---
|
|
@@ -193,26 +201,28 @@ typed alternative), [camelcase-keys](https://www.npmjs.com/package/camelcase-key
|
|
|
193
201
|
`toSnakeCaseKeys`). Plain snake_case keys such as `user_id` become the same camelCase
|
|
194
202
|
keys in all of them. The table shows where they differ:
|
|
195
203
|
|
|
196
|
-
| | deep-case-crafter
|
|
197
|
-
| -------------------------------------------------- |
|
|
198
|
-
| `userID` to snake_case | `user_id`
|
|
199
|
-
| `HTTPResponse` to snake_case | `http_response`
|
|
200
|
-
| `isAValidUser` to snake_case | `is_a_valid_user`
|
|
201
|
-
| `addressLine1` to snake_case | `address_line1`
|
|
202
|
-
| Objects inside a `Map` or `Set` | keys converted
|
|
203
|
-
| Result type for `await res.json()`, an `any` input | `any`
|
|
204
|
-
| A `readonly string[]` field in the result type | `readonly string[]`
|
|
205
|
-
| A `[number, number]` field in the result type | `[number, number]`
|
|
206
|
-
| Leaving chosen keys unchanged | `exclude` paths, such as `'metadata.*'`
|
|
207
|
-
| Target cases | camel, snake, kebab, Pascal
|
|
204
|
+
| | deep-case-crafter | ts-case-convert | camelcase-keys, snakecase-keys | es-toolkit |
|
|
205
|
+
| -------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
206
|
+
| `userID` to snake_case | `user_id` | `user_i_d` | `user_id` | `user_id`, typed `user_i_d` |
|
|
207
|
+
| `HTTPResponse` to snake_case | `http_response` | `h_t_t_p_response` | `http_response` | `http_response`, typed `h_t_t_p_response` |
|
|
208
|
+
| `isAValidUser` to snake_case | `is_a_valid_user` | `is_a_valid_user` | `is_a_valid_user` | `is_a_valid_user` |
|
|
209
|
+
| `addressLine1` to snake_case | `address_line1` | `address_line_1`, or `address_line1` with `no-split-numbers` | `address_line1` | `address_line_1`, typed `address_line1` |
|
|
210
|
+
| Objects inside a `Map` or `Set` | keys converted | outside its documented scope | outside their documented scope | left as they are, as documented |
|
|
211
|
+
| Result type for `await res.json()`, an `any` input | `any` | a union that includes `Date` and `null`, so `.userId` does not compile | `any` | `any` |
|
|
212
|
+
| A `readonly string[]` field in the result type | `readonly string[]` | an object type whose `.map()` does not compile | `readonly string[]` | an object type: `.map()` compiles, `[0]` and `for...of` do not |
|
|
213
|
+
| A `[number, number]` field in the result type | `[number, number]` | `number[]` | `[number, number]` | `number[]` |
|
|
214
|
+
| Leaving chosen keys unchanged | `exclude` paths, such as `'metadata.*'` | no option | `exclude` keys by name or RegExp; camelcase-keys also `stopPaths` | no option |
|
|
215
|
+
| Target cases | camel, snake, kebab, Pascal, SCREAMING_SNAKE | camel, snake, Pascal, SCREAMING_SNAKE | camel and Pascal; snake | camel, snake, kebab, Pascal, SCREAMING_SNAKE |
|
|
208
216
|
|
|
209
217
|
Where the others are better or different:
|
|
210
218
|
|
|
211
219
|
- **Bundle size**: bundled and minified with esbuild, ts-case-convert's `objectToCamel`
|
|
212
220
|
and `objectToSnake` take about 1.1 KB gzipped, deep-case-crafter's `transform` about
|
|
213
|
-
2.
|
|
214
|
-
- **
|
|
215
|
-
|
|
221
|
+
2.8 KB.
|
|
222
|
+
- **CONSTANT_CASE input**: camelcase-keys, snakecase-keys and es-toolkit also read
|
|
223
|
+
SCREAMING_SNAKE_CASE keys, so `USER_ID` becomes `userId` or `user_id`.
|
|
224
|
+
deep-case-crafter converts to CONSTANT_CASE, but leaves keys such as `USER_ID` and
|
|
225
|
+
`ID` unchanged (see [Key Preservation Rules](#key-preservation-rules)).
|
|
216
226
|
- **Single strings**: like `transformKey`, ts-case-convert's `toSnake('userId')` and
|
|
217
227
|
es-toolkit's `snakeCase('userId')` return `'user_id'`. es-toolkit's functions also
|
|
218
228
|
convert text, such as `'Hello World'` → `'hello_world'`, which `transformKey` returns
|
|
@@ -231,8 +241,8 @@ Where the others are better or different:
|
|
|
231
241
|
|
|
232
242
|
## Acronym-aware conversion
|
|
233
243
|
|
|
234
|
-
When converting **to** `snake_case` or `
|
|
235
|
-
are treated as whole words instead of being split letter-by-letter:
|
|
244
|
+
When converting **to** `snake_case`, `kebab-case` or `CONSTANT_CASE`, runs of capitals
|
|
245
|
+
(acronyms) are treated as whole words instead of being split letter-by-letter:
|
|
236
246
|
|
|
237
247
|
```typescript
|
|
238
248
|
const input = { getHTTPResponse: 1, userID: 2, parseHTMLString: 3 };
|
|
@@ -276,7 +286,7 @@ console.log(result);
|
|
|
276
286
|
```
|
|
277
287
|
|
|
278
288
|
Words of two or more capitals, such as `ID`, `USD` or `GET`, are left unchanged, like
|
|
279
|
-
`
|
|
289
|
+
`CONSTANT_CASE` keys (see [Key Preservation Rules](#key-preservation-rules)). A single
|
|
280
290
|
capital, with or without digits (`S`, `S3`), is converted as a one-letter word.
|
|
281
291
|
|
|
282
292
|
Converting back to `PascalCase` cannot restore a lowercased acronym: `httpResponse`
|
|
@@ -295,15 +305,17 @@ The result **types** match all of this, so `result.get_http_response` and
|
|
|
295
305
|
|
|
296
306
|
- **data**: The data structure to transform (`object`, `array`, `Map`, or `Set`).
|
|
297
307
|
- **options**:
|
|
298
|
-
- `targetCase` (optional): `'camel' | 'snake' | 'pascal' | 'kebab'` (default: `'camel'`, also used for any falsy value such as `''`, `null` or `0`). Any other value throws a `TypeError`.
|
|
308
|
+
- `targetCase` (optional): `'camel' | 'snake' | 'pascal' | 'kebab' | 'constant'` (default: `'camel'`, also used for any falsy value such as `''`, `null` or `0`). Any other value throws a `TypeError`. `'constant'` is CONSTANT_CASE, also called SCREAMING_SNAKE_CASE: `userID` becomes `USER_ID`, split into words as for snake_case. It is a target case only: keys such as `USER_ID` or `ID` are left unchanged by every target case, so they do not convert back. A single capital, with or without digits, is still converted as a one-letter word: `'constant'` turns `p99` into `P99`, and camel, snake and kebab turn it back into `p99` (see [Key Preservation Rules](#key-preservation-rules)).
|
|
299
309
|
- `depth` (optional): **How many nesting levels to transform** (default: `Infinity` — the whole structure; `null` means the same). `1` transforms only the top-level keys, `2` the keys one level down as well, and so on. The values of objects, Maps and Sets are one level deeper; array items stay on their array's level. A value that is not a number (`NaN`, `'3'`, `3n`) throws a `TypeError`.
|
|
300
310
|
- `exclude` (optional): **Keys to leave unchanged**, as paths of keys joined with dots, such as `['users_by_id.*', 'metadata.*']` (default: none; `null` means the same). See [Leaving keys unchanged](#leaving-keys-unchanged). Anything but an array of such paths throws a `TypeError`.
|
|
301
|
-
- `
|
|
311
|
+
- `keepReferences` (optional): **Whether shared and circular references keep their shape** (default: `true`; `null` means the same). An object reached through several references then gives one result, and a circular reference points back into the result. Pass `false` for data that cannot contain such references, such as the output of `JSON.parse`, to skip that bookkeeping: inputs with many objects and arrays convert up to about twice as fast. Use it for tree-shaped data only: an object is then copied once for every path that leads to it, so shared objects inside shared objects multiply. Data with more than 256 nested objects, arrays, Maps and Sets on one path, as circular references always have, is converted again from the start with the bookkeeping, so it takes longer and getters can run twice. With a finite `depth`, circular references stop at the depth limit before that, and are copied once per path down to it. Anything but a boolean throws a `TypeError`.
|
|
312
|
+
- `sourceCase` (**deprecated**): has no effect. Every key's case is detected from the key itself, at runtime and in the result type. It is still accepted, and so is the 2.x form with explicit type arguments, `transform<Data, 'snake', 'camel'>(data, { sourceCase: 'snake', targetCase: 'camel' })`, except together with `depth`, `exclude` or `keepReferences` (leave the type arguments out to use them); the key types follow the runtime, not `sourceCase` (see [3.0.0 in the changelog](https://github.com/gyrgy/deep-case-crafter/blob/main/CHANGELOG.md#300---2026-10-25)).
|
|
302
313
|
|
|
303
314
|
Circular references are resolved to the transformed object. With a finite `depth`, a
|
|
304
315
|
cycle is transformed once per level down to the limit instead: the result grows with the
|
|
305
|
-
depth, and a depth of more than about a thousand levels on cyclic data
|
|
306
|
-
stack and
|
|
316
|
+
depth, and a depth of more than about a thousand levels on cyclic data can exceed the
|
|
317
|
+
call stack and throw a `RangeError` (see [Limits](#limits)). Omit `depth` for cyclic
|
|
318
|
+
data, or keep it small.
|
|
307
319
|
|
|
308
320
|
### Leaving keys unchanged
|
|
309
321
|
|
|
@@ -367,11 +379,14 @@ type of the `exclude` paths, such as `['metadata.*']`.
|
|
|
367
379
|
(without `sourceCase`), and options passed with only the input type given
|
|
368
380
|
(`transform<Data>(data, options)`). Leave the type arguments out to get the precise
|
|
369
381
|
type.
|
|
370
|
-
- When a library exports a value or function built on `transform
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
382
|
+
- When a library exports a value or function built on `transform` or `transformKey`,
|
|
383
|
+
annotate its result with `TransformResult<...>` or `TransformKey<...>`. Otherwise the
|
|
384
|
+
result type is written out into its declaration files. For data of a fixed type,
|
|
385
|
+
recursive types (trees, JSON values) are then cut off as `any`, and very large types or
|
|
386
|
+
symbol-keyed members fail to compile (TS7056, TS4118). For a target case typed
|
|
387
|
+
`StringCase`, generic data included, the result can cover only the cases that existed
|
|
388
|
+
when the library was built, not cases added later. A generic target case, as in
|
|
389
|
+
`<C extends StringCase>(targetCase: C)`, is not affected.
|
|
375
390
|
- Some values cannot be told apart by their types. `Error`, `URL`, boxed primitives,
|
|
376
391
|
iterators, module namespace objects and other built-ins whose types do not declare a
|
|
377
392
|
`Symbol.toStringTag` are returned untouched, but typed as plain objects with
|
|
@@ -396,15 +411,15 @@ transformKey('HTTPResponse', { targetCase: 'snake' }); // 'http_response'
|
|
|
396
411
|
transformKey('Hello World', { targetCase: 'snake' }); // 'Hello World'
|
|
397
412
|
```
|
|
398
413
|
|
|
399
|
-
It converts names, not text: a string that is not a
|
|
400
|
-
returned unchanged, such as `'Hello World'`, `'USER_ID'`, `'_id'` or a
|
|
401
|
-
[Key Preservation Rules](#key-preservation-rules)).
|
|
414
|
+
It converts names, not text: a string that is not a camelCase, PascalCase, snake_case or
|
|
415
|
+
kebab-case key is returned unchanged, such as `'Hello World'`, `'USER_ID'`, `'_id'` or a
|
|
416
|
+
UUID (see [Key Preservation Rules](#key-preservation-rules)).
|
|
402
417
|
|
|
403
418
|
The result type is the converted key. A string literal gives a literal, a union of keys
|
|
404
419
|
or of target cases gives the union of their results, and a `string` gives `string`.
|
|
405
420
|
`TransformKey<Key, TargetCase>` names it: `TransformKey<'userID', 'snake'>` is
|
|
406
421
|
`'user_id'`. Options that are not known exactly, such as an options object that may be
|
|
407
|
-
`undefined`, give a wider type: `string`, or the key in any of the
|
|
422
|
+
`undefined`, give a wider type: `string`, or the key in any of the target cases.
|
|
408
423
|
|
|
409
424
|
To use it in a callback, wrap it in a function:
|
|
410
425
|
`keys.map((key) => transformKey(key, { targetCase: 'snake' }))`.
|
|
@@ -446,6 +461,10 @@ A key is left **completely unchanged** when it:
|
|
|
446
461
|
- **is not written in one recognisable case**, e.g. `mixed_Case`, `USER_ID`, `a_b-c`, or
|
|
447
462
|
is a word of two or more capitals, e.g. `ID`, `USD`, `B2B`.
|
|
448
463
|
|
|
464
|
+
CONSTANT_CASE keys such as `USER_ID` fall under the last rule, so every target case keeps
|
|
465
|
+
them: `'constant'` is a target case only. A single capital, with or without digits
|
|
466
|
+
(`X`, `P99`), is not kept: it is converted as a one-letter word.
|
|
467
|
+
|
|
449
468
|
Trailing digits are kept but treated as part of the word, so they do **not** by
|
|
450
469
|
themselves preserve a key — `address_1` becomes `address1`, and the single word
|
|
451
470
|
`item2` stays `item2`.
|
|
@@ -476,14 +495,36 @@ Worth knowing:
|
|
|
476
495
|
`userId`), the last one wins.
|
|
477
496
|
- Every property is read once, in order, and its value is transformed before the next one
|
|
478
497
|
is read; getters run during the transformation. Maps are read through their iterator.
|
|
498
|
+
With `keepReferences: false`, data with more than 256 nested objects, arrays, Maps and
|
|
499
|
+
Sets on one path is read a second time.
|
|
479
500
|
- With `depth`, values below the limit are returned as they are: the result shares them
|
|
480
501
|
with the input instead of copying them. An object reached at several levels is
|
|
481
502
|
transformed once per level.
|
|
482
503
|
- With `exclude`, an object reached through several keys is transformed once for each
|
|
483
504
|
set of paths that match below them: one result when the same paths continue below
|
|
484
505
|
every key, one per set otherwise.
|
|
485
|
-
- The transformation is recursive
|
|
486
|
-
|
|
506
|
+
- The transformation is recursive, so very deeply nested data hits a limit: see
|
|
507
|
+
[Limits](#limits).
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## Limits
|
|
512
|
+
|
|
513
|
+
Measured with Node 24 on an Apple M4 Pro, with Node's default stack and memory sizes:
|
|
514
|
+
|
|
515
|
+
- **Nesting depth.** The conversion is recursive. Depending on the shape, the first call
|
|
516
|
+
in a process converts 1,300 to 2,400 levels of nesting (nested Maps the fewest, nested
|
|
517
|
+
objects the most), and once the code is warm about 2,600 (nested arrays) to 4,400;
|
|
518
|
+
deeper data throws a `RangeError`. API data nests far less, but untrusted input can nest
|
|
519
|
+
deeper (`JSON.parse` accepts millions of levels), so catch the error where such input is
|
|
520
|
+
converted.
|
|
521
|
+
- **Size.** There is no fixed limit on keys, objects or array items. The result is a full
|
|
522
|
+
copy, so the input and the result must both fit in memory: converting 1 million records
|
|
523
|
+
with 30 million keys (725 MB in memory) needed 1.5 GB more at its peak, and 1.1 GB with
|
|
524
|
+
`keepReferences: false`. When memory runs out, Node ends the process, which cannot be
|
|
525
|
+
caught.
|
|
526
|
+
- **Long keys.** Conversion time grows linearly with a key's length: about 20 ms for a key
|
|
527
|
+
of a million characters.
|
|
487
528
|
|
|
488
529
|
---
|
|
489
530
|
|
|
@@ -494,7 +535,8 @@ stable. Key outputs change only to fix bugs, and every change is listed in the
|
|
|
494
535
|
[changelog](https://github.com/gyrgy/deep-case-crafter/blob/main/CHANGELOG.md): if you
|
|
495
536
|
store converted keys, such as database columns, check it before upgrading. A minor
|
|
496
537
|
version can add an option, which `Required<TransformOptions>` and
|
|
497
|
-
`Required<TransformKeyOptions>` then require
|
|
538
|
+
`Required<TransformKeyOptions>` then require, or a target case, which code that handles
|
|
539
|
+
every member of `StringCase` then has to handle too.
|
|
498
540
|
|
|
499
541
|
### Upgrading to v3
|
|
500
542
|
|