@helpers4/object 3.0.2 → 3.0.3

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/README.md CHANGED
@@ -12,6 +12,7 @@ A set of helpers for working with URLs.
12
12
  [https://helpers4.dev/typescript/categories/object/](https://helpers4.dev/typescript/categories/object/)
13
13
 
14
14
  <!-- AUTOMATIC-METHODS -->
15
+ - camelCaseKeys
15
16
  - clone
16
17
  - cloneDeep
17
18
  - compact
@@ -24,16 +25,22 @@ A set of helpers for working with URLs.
24
25
  - invert
25
26
  - isEmpty
26
27
  - isNonEmpty
28
+ - kebabCaseKeys
27
29
  - map
30
+ - mapDeep
28
31
  - mergeDeep
29
32
  - omit
30
33
  - omitBy
31
34
  - parsePropertyPath
35
+ - pascalCaseKeys
32
36
  - pick
33
37
  - pickBy
34
38
  - removeUndefinedNull
35
39
  - safeJsonParse
36
40
  - set
41
+ - snakeCaseKeys
42
+ - sortKeys
43
+ - titleCaseKeys
37
44
  - unflatten
38
45
  - unset
39
46
  - update
@@ -50,11 +57,13 @@ A set of helpers for working with URLs.
50
57
  - [function](../function)
51
58
  - [guard](../guard)
52
59
  - [id](../id)
60
+ - [map](../map)
53
61
  - [markdown](../markdown)
54
62
  - [node](../node)
55
63
  - [number](../number)
56
64
  - [observable](../observable)
57
65
  - [promise](../promise)
66
+ - [set](../set)
58
67
  - [string](../string)
59
68
  - [type](../type)
60
69
  - [url](../url)
@@ -74,12 +83,14 @@ A set of helpers for working with URLs.
74
83
  | function | [@helpers4/function](https://www.npmjs.com/package/@helpers4/function) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/function) | Function utilities and type guards |
75
84
  | guard | [@helpers4/guard](https://www.npmjs.com/package/@helpers4/guard) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/guard) | Runtime type guard utilities |
76
85
  | id | [@helpers4/id](https://www.npmjs.com/package/@helpers4/id) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/id) | Unique identifier generation utilities |
86
+ | map | [@helpers4/map](https://www.npmjs.com/package/@helpers4/map) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/map) | Native Map manipulation utilities |
77
87
  | markdown | [@helpers4/markdown](https://www.npmjs.com/package/@helpers4/markdown) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/markdown) | Markdown formatting and escaping utilities |
78
88
  | node | [@helpers4/node](https://www.npmjs.com/package/@helpers4/node) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/node) | Node.js-specific utilities |
79
89
  | number | [@helpers4/number](https://www.npmjs.com/package/@helpers4/number) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/number) | Number utility functions and validation |
80
90
  | object | [@helpers4/object](https://www.npmjs.com/package/@helpers4/object) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/object) | Object manipulation utilities |
81
91
  | observable | [@helpers4/observable](https://www.npmjs.com/package/@helpers4/observable) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/observable) | Observable utilities and combinators |
82
92
  | promise | [@helpers4/promise](https://www.npmjs.com/package/@helpers4/promise) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/promise) | Promise utilities and error handling |
93
+ | set | [@helpers4/set](https://www.npmjs.com/package/@helpers4/set) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/set) | Native Set manipulation utilities |
83
94
  | string | [@helpers4/string](https://www.npmjs.com/package/@helpers4/string) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/string) | String manipulation and formatting utilities |
84
95
  | type | [@helpers4/type](https://www.npmjs.com/package/@helpers4/type) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/type) | Compile-time TypeScript utility types |
85
96
  | url | [@helpers4/url](https://www.npmjs.com/package/@helpers4/url) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/url) | URL parsing and manipulation utilities |
package/lib/index.d.ts CHANGED
@@ -1,3 +1,24 @@
1
+ /**
2
+ * This file is part of helpers4.
3
+ * Copyright (C) 2025 baxyz
4
+ * SPDX-License-Identifier: LGPL-3.0-or-later
5
+ */
6
+ /**
7
+ * Recursively transforms every key of a plain object (including keys nested inside arrays and
8
+ * nested objects) to camelCase. Handles snake_case, kebab-case, PascalCase, and space-separated
9
+ * keys. Non-plain-object values (arrays' items aside) — `Date`, `Map`, `Set`, class instances,
10
+ * primitives — are left untouched, only their position is walked into. An entry whose
11
+ * transformed key is a prototype-polluting string (`__proto__`, `constructor`, `prototype`) is
12
+ * silently skipped, same as the rest of `@helpers4/object`.
13
+ * @param value - The object (or array of objects) to transform
14
+ * @returns A new value with every plain-object key converted to camelCase
15
+ * @example
16
+ * camelCaseKeys({ user_name: 'Alice', home_address: { zip_code: '12345' } })
17
+ * // => { userName: 'Alice', homeAddress: { zipCode: '12345' } }
18
+ * @since 3.0.3
19
+ */
20
+ declare function camelCaseKeys<T>(value: T): T;
21
+
1
22
  /**
2
23
  * This file is part of helpers4.
3
24
  * Copyright (C) 2025 baxyz
@@ -530,6 +551,27 @@ declare function isEmpty(value: Record<PropertyKey, unknown> | null | undefined)
530
551
  */
531
552
  declare function isNonEmpty(value: Record<PropertyKey, unknown> | null | undefined): value is Record<PropertyKey, unknown>;
532
553
 
554
+ /**
555
+ * This file is part of helpers4.
556
+ * Copyright (C) 2025 baxyz
557
+ * SPDX-License-Identifier: LGPL-3.0-or-later
558
+ */
559
+ /**
560
+ * Recursively transforms every key of a plain object (including keys nested inside arrays and
561
+ * nested objects) to kebab-case. Handles snake_case, camelCase, PascalCase, and space-separated
562
+ * keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
563
+ * untouched, only their position is walked into. An entry whose transformed key is a
564
+ * prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
565
+ * same as the rest of `@helpers4/object`.
566
+ * @param value - The object (or array of objects) to transform
567
+ * @returns A new value with every plain-object key converted to kebab-case
568
+ * @example
569
+ * kebabCaseKeys({ userName: 'Alice', homeAddress: { zipCode: '12345' } })
570
+ * // => { 'user-name': 'Alice', 'home-address': { 'zip-code': '12345' } }
571
+ * @since 3.0.3
572
+ */
573
+ declare function kebabCaseKeys<T>(value: T): T;
574
+
533
575
  /**
534
576
  * This file is part of helpers4.
535
577
  * Copyright (C) 2025 baxyz
@@ -565,6 +607,46 @@ declare function isNonEmpty(value: Record<PropertyKey, unknown> | null | undefin
565
607
  */
566
608
  declare function map<TObj extends Record<string, unknown>, TVal = TObj[keyof TObj], TKey extends PropertyKey = keyof TObj>(obj: TObj | null | undefined, mapValue?: (value: TObj[keyof TObj], key: keyof TObj) => TVal, mapKey?: (key: keyof TObj, value: TObj[keyof TObj]) => TKey): Record<TKey, TVal>;
567
609
 
610
+ /**
611
+ * This file is part of helpers4.
612
+ * Copyright (C) 2025 baxyz
613
+ * SPDX-License-Identifier: LGPL-3.0-or-later
614
+ */
615
+ /**
616
+ * Recursively transforms the keys and/or values of a plain object — the deep counterpart to
617
+ * {@link map}, which only transforms the top level. Descends into nested plain objects and
618
+ * arrays of them; `Date`, `Map`, `Set`, class instances, and primitives are left untouched
619
+ * (only their position is walked into, matching {@link cloneDeep}'s notion of what counts
620
+ * as a plain-data node worth recursing into).
621
+ *
622
+ * Both callbacks are optional and default to identity (no transformation). `mapKey` is called
623
+ * with the *original* key and value (before any recursive value transform); `mapValue` receives
624
+ * the already-recursed value. Entries whose mapped key is a prototype-polluting string
625
+ * (`__proto__`, `constructor`, `prototype`) are silently skipped, same as {@link map}.
626
+ *
627
+ * Array elements also go through `mapValue` (with their stringified index as `key`, since
628
+ * arrays have no property keys of their own) — so values inside a plain array, not just object
629
+ * properties, get transformed too. `mapKey` is never called for array elements, since there is
630
+ * no key to rename.
631
+ *
632
+ * @param obj - The value to transform (typically an object, or an array of objects)
633
+ * @param mapValue - Callback called with `(value, key)` for each entry's (already-recursed) value.
634
+ * For array elements, `key` is the element's index as a string. Defaults to identity.
635
+ * @param mapKey - Callback called with `(key, value)` for each object entry. Defaults to identity.
636
+ * @returns A new value with every plain-object key/value transformed
637
+ * @example
638
+ * mapDeep({ a: { b: 1 } }, v => (typeof v === 'number' ? v * 10 : v))
639
+ * // => { a: { b: 10 } }
640
+ * @example
641
+ * mapDeep({ user_name: 'Alice' }, undefined, key => key.toUpperCase())
642
+ * // => { USER_NAME: 'Alice' }
643
+ * @example
644
+ * mapDeep({ tags: [1, 2, 3] }, v => (typeof v === 'number' ? v * 10 : v))
645
+ * // => { tags: [10, 20, 30] }
646
+ * @since 3.0.3
647
+ */
648
+ declare function mapDeep<T>(obj: T, mapValue?: (value: unknown, key: string) => unknown, mapKey?: (key: string, value: unknown) => PropertyKey): T;
649
+
568
650
  /**
569
651
  * This file is part of helpers4.
570
652
  * Copyright (C) 2025 baxyz
@@ -700,6 +782,27 @@ declare function omitBy<T extends Record<string, unknown>>(obj: T | undefined |
700
782
  */
701
783
  declare function parsePropertyPath(path: string): readonly (string | number)[];
702
784
 
785
+ /**
786
+ * This file is part of helpers4.
787
+ * Copyright (C) 2025 baxyz
788
+ * SPDX-License-Identifier: LGPL-3.0-or-later
789
+ */
790
+ /**
791
+ * Recursively transforms every key of a plain object (including keys nested inside arrays and
792
+ * nested objects) to PascalCase. Handles camelCase, snake_case, kebab-case, and space-separated
793
+ * keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
794
+ * untouched, only their position is walked into. An entry whose transformed key is a
795
+ * prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
796
+ * same as the rest of `@helpers4/object`.
797
+ * @param value - The object (or array of objects) to transform
798
+ * @returns A new value with every plain-object key converted to PascalCase
799
+ * @example
800
+ * pascalCaseKeys({ user_name: 'Alice', home_address: { zip_code: '12345' } })
801
+ * // => { UserName: 'Alice', HomeAddress: { ZipCode: '12345' } }
802
+ * @since 3.0.3
803
+ */
804
+ declare function pascalCaseKeys<T>(value: T): T;
805
+
703
806
  /**
704
807
  * This file is part of helpers4.
705
808
  * Copyright (C) 2025 baxyz
@@ -844,6 +947,76 @@ declare function safeJsonParse<T>(input: string, fallback: T): T;
844
947
  */
845
948
  declare function set<T extends object, const P extends string | readonly PropertyKey[], V extends DeepGet<T, ParsePath<P>>>(obj: T, path: P, value: V): string extends P ? T : readonly PropertyKey[] extends P ? T : ContainsUnsafeStringKey<ParsePath<P>> extends true ? T : DeepSet<T, ParsePath<P>, V>;
846
949
 
950
+ /**
951
+ * This file is part of helpers4.
952
+ * Copyright (C) 2025 baxyz
953
+ * SPDX-License-Identifier: LGPL-3.0-or-later
954
+ */
955
+ /**
956
+ * Recursively transforms every key of a plain object (including keys nested inside arrays and
957
+ * nested objects) to snake_case. Handles camelCase, PascalCase, kebab-case, and space-separated
958
+ * keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
959
+ * untouched, only their position is walked into. An entry whose transformed key is a
960
+ * prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
961
+ * same as the rest of `@helpers4/object`.
962
+ * @param value - The object (or array of objects) to transform
963
+ * @returns A new value with every plain-object key converted to snake_case
964
+ * @example
965
+ * snakeCaseKeys({ userName: 'Alice', homeAddress: { zipCode: '12345' } })
966
+ * // => { user_name: 'Alice', home_address: { zip_code: '12345' } }
967
+ * @since 3.0.3
968
+ */
969
+ declare function snakeCaseKeys<T>(value: T): T;
970
+
971
+ /**
972
+ * This file is part of helpers4.
973
+ * Copyright (C) 2025 baxyz
974
+ * SPDX-License-Identifier: LGPL-3.0-or-later
975
+ */
976
+ /**
977
+ * Creates a new object with the same entries as the input, but with its own keys sorted.
978
+ * Shallow only — nested objects are copied as-is, their keys are not re-sorted.
979
+ * Key order matters for things like `JSON.stringify` diffs and snapshot stability; this does
980
+ * not change equality (`{a:1,b:2}` and `{b:2,a:1}` are already equal), just iteration/serialization order.
981
+ * Note: integer-index-like keys (`"0"`, `"1"`, `"42"`...) are always iterated first, in numeric
982
+ * order, by the JS engine itself — no object key ordering can override that language behavior.
983
+ * A prototype-polluting key (`__proto__`, `constructor`, `prototype`) is silently skipped,
984
+ * same as the rest of `@helpers4/object`.
985
+ * @param obj - The object whose keys to sort
986
+ * @param compareFn - Optional custom comparator, passed directly to `Array.prototype.sort`
987
+ * @returns A new object with the same entries, own keys in sorted order
988
+ * @example
989
+ * sortKeys({ b: 2, a: 1, c: 3 })
990
+ * // => { a: 1, b: 2, c: 3 }
991
+ * @since 3.0.3
992
+ */
993
+ declare function sortKeys<T extends Record<string, unknown>>(obj: T, compareFn?: (a: string, b: string) => number): T;
994
+
995
+ /**
996
+ * This file is part of helpers4.
997
+ * Copyright (C) 2025 baxyz
998
+ * SPDX-License-Identifier: LGPL-3.0-or-later
999
+ */
1000
+ /**
1001
+ * Recursively transforms every key of a plain object (including keys nested inside arrays and
1002
+ * nested objects) to Title Case. Handles camelCase, snake_case, kebab-case, and space-separated
1003
+ * keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
1004
+ * untouched, only their position is walked into. An entry whose transformed key is a
1005
+ * prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
1006
+ * same as the rest of `@helpers4/object`.
1007
+ *
1008
+ * Note: unlike the other `*CaseKeys` variants, `titleCase` joins words with spaces (e.g.
1009
+ * `'User Name'`) — those keys are only usable via bracket access (`obj['User Name']`), not dot
1010
+ * notation. Useful for display-facing structures (form labels, table headers), not wire formats.
1011
+ * @param value - The object (or array of objects) to transform
1012
+ * @returns A new value with every plain-object key converted to Title Case
1013
+ * @example
1014
+ * titleCaseKeys({ user_name: 'Alice', home_address: { zip_code: '12345' } })
1015
+ * // => { 'User Name': 'Alice', 'Home Address': { 'Zip Code': '12345' } }
1016
+ * @since 3.0.3
1017
+ */
1018
+ declare function titleCaseKeys<T>(value: T): T;
1019
+
847
1020
  /**
848
1021
  * This file is part of helpers4.
849
1022
  * Copyright (C) 2025 baxyz
@@ -927,5 +1100,5 @@ declare function unset<T extends object>(obj: T, path: string | readonly Propert
927
1100
  */
928
1101
  declare function update<T extends object, const P extends string | readonly PropertyKey[], V extends DeepGet<T, ParsePath<P>>>(obj: T, path: P, updater: (current: DeepGet<T, ParsePath<P>> | undefined) => V): string extends P ? T : readonly PropertyKey[] extends P ? T : ContainsUnsafeStringKey<ParsePath<P>> extends true ? T : DeepSet<T, ParsePath<P>, V>;
929
1102
 
930
- export { clone, cloneDeep, compact, diff, equalsDeep, equalsShallow, flatten, get, groupBy, invert, isEmpty, isNonEmpty, map, mergeDeep, omit, omitBy, parsePropertyPath, pick, pickBy, removeUndefinedNull, safeJsonParse, set, unflatten, unset, update };
1103
+ export { camelCaseKeys, clone, cloneDeep, compact, diff, equalsDeep, equalsShallow, flatten, get, groupBy, invert, isEmpty, isNonEmpty, kebabCaseKeys, map, mapDeep, mergeDeep, omit, omitBy, parsePropertyPath, pascalCaseKeys, pick, pickBy, removeUndefinedNull, safeJsonParse, set, snakeCaseKeys, sortKeys, titleCaseKeys, unflatten, unset, update };
931
1104
  export type { DiffOptions, DiffResult };
package/lib/index.js CHANGED
@@ -1,9 +1,128 @@
1
+ //#region helpers/string/camelCase.ts
2
+ function camelCase(str) {
3
+ if (str === void 0 || str === null) return str;
4
+ const words = str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").split(/[\s_-]+/).filter(Boolean);
5
+ if (words.length === 0) return "";
6
+ return words[0].toLowerCase() + words.slice(1).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
7
+ }
1
8
  var UNSAFE_KEYS = /* @__PURE__ */ new Set([
2
9
  "__proto__",
3
10
  "constructor",
4
11
  "prototype"
5
12
  ]);
6
13
  //#endregion
14
+ //#region helpers/guard/isPlainObject.ts
15
+ /**
16
+ * This file is part of helpers4.
17
+ * Copyright (C) 2025 baxyz
18
+ * SPDX-License-Identifier: LGPL-3.0-or-later
19
+ */
20
+ /**
21
+ * Checks if a value is a plain object.
22
+ *
23
+ * A plain object is created by `{}`, `new Object()`, or `Object.create(null)`.
24
+ * Returns `false` for arrays, Date, Map, Set, RegExp, class instances, etc.
25
+ *
26
+ * @param value - The value to check
27
+ * @returns True if value is a plain object
28
+ * @example
29
+ * isPlainObject({}) // => true
30
+ * isPlainObject({ a: 1 }) // => true
31
+ * isPlainObject(new Date()) // => false
32
+ * isPlainObject([]) // => false
33
+ * isPlainObject(null) // => false
34
+ * @since 2.0.0
35
+ */
36
+ function isPlainObject(value) {
37
+ if (value === null || typeof value !== "object") return false;
38
+ const proto = Object.getPrototypeOf(value);
39
+ return proto === Object.prototype || proto === null;
40
+ }
41
+ //#endregion
42
+ //#region helpers/object/mapDeep.ts
43
+ /**
44
+ * This file is part of helpers4.
45
+ * Copyright (C) 2025 baxyz
46
+ * SPDX-License-Identifier: LGPL-3.0-or-later
47
+ */
48
+ function transform(value, mapValue, mapKey) {
49
+ if (Array.isArray(value)) return value.map((item, index) => {
50
+ const recursed = transform(item, mapValue, mapKey);
51
+ return mapValue ? mapValue(recursed, String(index)) : recursed;
52
+ });
53
+ if (isPlainObject(value)) {
54
+ const result = {};
55
+ for (const [key, val] of Object.entries(value)) {
56
+ const newKey = mapKey ? mapKey(key, val) : key;
57
+ if (typeof newKey === "string" && UNSAFE_KEYS.has(newKey)) continue;
58
+ const recursed = transform(val, mapValue, mapKey);
59
+ result[newKey] = mapValue ? mapValue(recursed, key) : recursed;
60
+ }
61
+ return result;
62
+ }
63
+ return value;
64
+ }
65
+ /**
66
+ * Recursively transforms the keys and/or values of a plain object — the deep counterpart to
67
+ * {@link map}, which only transforms the top level. Descends into nested plain objects and
68
+ * arrays of them; `Date`, `Map`, `Set`, class instances, and primitives are left untouched
69
+ * (only their position is walked into, matching {@link cloneDeep}'s notion of what counts
70
+ * as a plain-data node worth recursing into).
71
+ *
72
+ * Both callbacks are optional and default to identity (no transformation). `mapKey` is called
73
+ * with the *original* key and value (before any recursive value transform); `mapValue` receives
74
+ * the already-recursed value. Entries whose mapped key is a prototype-polluting string
75
+ * (`__proto__`, `constructor`, `prototype`) are silently skipped, same as {@link map}.
76
+ *
77
+ * Array elements also go through `mapValue` (with their stringified index as `key`, since
78
+ * arrays have no property keys of their own) — so values inside a plain array, not just object
79
+ * properties, get transformed too. `mapKey` is never called for array elements, since there is
80
+ * no key to rename.
81
+ *
82
+ * @param obj - The value to transform (typically an object, or an array of objects)
83
+ * @param mapValue - Callback called with `(value, key)` for each entry's (already-recursed) value.
84
+ * For array elements, `key` is the element's index as a string. Defaults to identity.
85
+ * @param mapKey - Callback called with `(key, value)` for each object entry. Defaults to identity.
86
+ * @returns A new value with every plain-object key/value transformed
87
+ * @example
88
+ * mapDeep({ a: { b: 1 } }, v => (typeof v === 'number' ? v * 10 : v))
89
+ * // => { a: { b: 10 } }
90
+ * @example
91
+ * mapDeep({ user_name: 'Alice' }, undefined, key => key.toUpperCase())
92
+ * // => { USER_NAME: 'Alice' }
93
+ * @example
94
+ * mapDeep({ tags: [1, 2, 3] }, v => (typeof v === 'number' ? v * 10 : v))
95
+ * // => { tags: [10, 20, 30] }
96
+ * @since 3.0.3
97
+ */
98
+ function mapDeep(obj, mapValue, mapKey) {
99
+ return transform(obj, mapValue, mapKey);
100
+ }
101
+ //#endregion
102
+ //#region helpers/object/camelCaseKeys.ts
103
+ /**
104
+ * This file is part of helpers4.
105
+ * Copyright (C) 2025 baxyz
106
+ * SPDX-License-Identifier: LGPL-3.0-or-later
107
+ */
108
+ /**
109
+ * Recursively transforms every key of a plain object (including keys nested inside arrays and
110
+ * nested objects) to camelCase. Handles snake_case, kebab-case, PascalCase, and space-separated
111
+ * keys. Non-plain-object values (arrays' items aside) — `Date`, `Map`, `Set`, class instances,
112
+ * primitives — are left untouched, only their position is walked into. An entry whose
113
+ * transformed key is a prototype-polluting string (`__proto__`, `constructor`, `prototype`) is
114
+ * silently skipped, same as the rest of `@helpers4/object`.
115
+ * @param value - The object (or array of objects) to transform
116
+ * @returns A new value with every plain-object key converted to camelCase
117
+ * @example
118
+ * camelCaseKeys({ user_name: 'Alice', home_address: { zip_code: '12345' } })
119
+ * // => { userName: 'Alice', homeAddress: { zipCode: '12345' } }
120
+ * @since 3.0.3
121
+ */
122
+ function camelCaseKeys(value) {
123
+ return mapDeep(value, void 0, camelCase);
124
+ }
125
+ //#endregion
7
126
  //#region helpers/object/clone.ts
8
127
  /**
9
128
  * This file is part of helpers4.
@@ -98,34 +217,6 @@ function compact(obj) {
98
217
  return result;
99
218
  }
100
219
  //#endregion
101
- //#region helpers/guard/isPlainObject.ts
102
- /**
103
- * This file is part of helpers4.
104
- * Copyright (C) 2025 baxyz
105
- * SPDX-License-Identifier: LGPL-3.0-or-later
106
- */
107
- /**
108
- * Checks if a value is a plain object.
109
- *
110
- * A plain object is created by `{}`, `new Object()`, or `Object.create(null)`.
111
- * Returns `false` for arrays, Date, Map, Set, RegExp, class instances, etc.
112
- *
113
- * @param value - The value to check
114
- * @returns True if value is a plain object
115
- * @example
116
- * isPlainObject({}) // => true
117
- * isPlainObject({ a: 1 }) // => true
118
- * isPlainObject(new Date()) // => false
119
- * isPlainObject([]) // => false
120
- * isPlainObject(null) // => false
121
- * @since 2.0.0
122
- */
123
- function isPlainObject(value) {
124
- if (value === null || typeof value !== "object") return false;
125
- const proto = Object.getPrototypeOf(value);
126
- return proto === Object.prototype || proto === null;
127
- }
128
- //#endregion
129
220
  //#region helpers/array/equalsDeep.ts
130
221
  /**
131
222
  * This file is part of helpers4.
@@ -530,6 +621,32 @@ function flatten(obj) {
530
621
  return result;
531
622
  }
532
623
  //#endregion
624
+ //#region helpers/_shared/_walkPropertyPath.ts
625
+ /**
626
+ * This file is part of helpers4.
627
+ * Copyright (C) 2025 baxyz
628
+ * SPDX-License-Identifier: LGPL-3.0-or-later
629
+ */
630
+ /**
631
+ * Walks a value through a sequence of keys, returning the resolved value or `undefined` if any
632
+ * step along the way is missing or not an object. Pure traversal only — string-path parsing
633
+ * (`'a.b.c'` → keys) and default-value substitution are the caller's job.
634
+ *
635
+ * Shared by `object/get.ts` and any `*By`-style accessor elsewhere that needs the same
636
+ * "resolve a path against a value" runtime behavior — kept dependency-free (per the `_shared/`
637
+ * convention) so it can be used from any category without adding a cross-category import for
638
+ * this specific piece.
639
+ * @ignore
640
+ */
641
+ function walkPropertyPath(value, keys) {
642
+ let result = value;
643
+ for (const key of keys) {
644
+ if (result == null || typeof result !== "object") return;
645
+ result = result[key];
646
+ }
647
+ return result;
648
+ }
649
+ //#endregion
533
650
  //#region helpers/object/parsePropertyPath.ts
534
651
  /**
535
652
  * This file is part of helpers4.
@@ -600,12 +717,7 @@ function parseUncached(path) {
600
717
  */
601
718
  function get(obj, path, defaultValue) {
602
719
  if (obj == null) return defaultValue;
603
- const keys = typeof path === "string" ? parsePropertyPath(path) : path;
604
- let result = obj;
605
- for (const key of keys) {
606
- if (result == null || typeof result !== "object") return defaultValue;
607
- result = result[key];
608
- }
720
+ const result = walkPropertyPath(obj, typeof path === "string" ? parsePropertyPath(path) : path);
609
721
  return result !== void 0 ? result : defaultValue;
610
722
  }
611
723
  //#endregion
@@ -737,6 +849,36 @@ function isNonEmpty(value) {
737
849
  return value != null && Object.keys(value).length > 0;
738
850
  }
739
851
  //#endregion
852
+ //#region helpers/string/kebabCase.ts
853
+ function kebabCase(str) {
854
+ if (str === void 0 || str === null) return str;
855
+ return str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").split(/[\s_-]+/).filter(Boolean).join("-").toLowerCase();
856
+ }
857
+ //#endregion
858
+ //#region helpers/object/kebabCaseKeys.ts
859
+ /**
860
+ * This file is part of helpers4.
861
+ * Copyright (C) 2025 baxyz
862
+ * SPDX-License-Identifier: LGPL-3.0-or-later
863
+ */
864
+ /**
865
+ * Recursively transforms every key of a plain object (including keys nested inside arrays and
866
+ * nested objects) to kebab-case. Handles snake_case, camelCase, PascalCase, and space-separated
867
+ * keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
868
+ * untouched, only their position is walked into. An entry whose transformed key is a
869
+ * prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
870
+ * same as the rest of `@helpers4/object`.
871
+ * @param value - The object (or array of objects) to transform
872
+ * @returns A new value with every plain-object key converted to kebab-case
873
+ * @example
874
+ * kebabCaseKeys({ userName: 'Alice', homeAddress: { zipCode: '12345' } })
875
+ * // => { 'user-name': 'Alice', 'home-address': { 'zip-code': '12345' } }
876
+ * @since 3.0.3
877
+ */
878
+ function kebabCaseKeys(value) {
879
+ return mapDeep(value, void 0, kebabCase);
880
+ }
881
+ //#endregion
740
882
  //#region helpers/object/map.ts
741
883
  /**
742
884
  * This file is part of helpers4.
@@ -850,6 +992,36 @@ function omitBy(obj, predicate) {
850
992
  return result;
851
993
  }
852
994
  //#endregion
995
+ //#region helpers/string/pascalCase.ts
996
+ function pascalCase(str) {
997
+ if (str === void 0 || str === null) return str;
998
+ return str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").split(/[\s_-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
999
+ }
1000
+ //#endregion
1001
+ //#region helpers/object/pascalCaseKeys.ts
1002
+ /**
1003
+ * This file is part of helpers4.
1004
+ * Copyright (C) 2025 baxyz
1005
+ * SPDX-License-Identifier: LGPL-3.0-or-later
1006
+ */
1007
+ /**
1008
+ * Recursively transforms every key of a plain object (including keys nested inside arrays and
1009
+ * nested objects) to PascalCase. Handles camelCase, snake_case, kebab-case, and space-separated
1010
+ * keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
1011
+ * untouched, only their position is walked into. An entry whose transformed key is a
1012
+ * prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
1013
+ * same as the rest of `@helpers4/object`.
1014
+ * @param value - The object (or array of objects) to transform
1015
+ * @returns A new value with every plain-object key converted to PascalCase
1016
+ * @example
1017
+ * pascalCaseKeys({ user_name: 'Alice', home_address: { zip_code: '12345' } })
1018
+ * // => { UserName: 'Alice', HomeAddress: { ZipCode: '12345' } }
1019
+ * @since 3.0.3
1020
+ */
1021
+ function pascalCaseKeys(value) {
1022
+ return mapDeep(value, void 0, pascalCase);
1023
+ }
1024
+ //#endregion
853
1025
  //#region helpers/object/pick.ts
854
1026
  /**
855
1027
  * This file is part of helpers4.
@@ -971,6 +1143,103 @@ function set(obj, path, value) {
971
1143
  return obj;
972
1144
  }
973
1145
  //#endregion
1146
+ //#region helpers/string/snakeCase.ts
1147
+ function snakeCase(str) {
1148
+ if (str === void 0 || str === null) return str;
1149
+ return str.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
1150
+ }
1151
+ //#endregion
1152
+ //#region helpers/object/snakeCaseKeys.ts
1153
+ /**
1154
+ * This file is part of helpers4.
1155
+ * Copyright (C) 2025 baxyz
1156
+ * SPDX-License-Identifier: LGPL-3.0-or-later
1157
+ */
1158
+ /**
1159
+ * Recursively transforms every key of a plain object (including keys nested inside arrays and
1160
+ * nested objects) to snake_case. Handles camelCase, PascalCase, kebab-case, and space-separated
1161
+ * keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
1162
+ * untouched, only their position is walked into. An entry whose transformed key is a
1163
+ * prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
1164
+ * same as the rest of `@helpers4/object`.
1165
+ * @param value - The object (or array of objects) to transform
1166
+ * @returns A new value with every plain-object key converted to snake_case
1167
+ * @example
1168
+ * snakeCaseKeys({ userName: 'Alice', homeAddress: { zipCode: '12345' } })
1169
+ * // => { user_name: 'Alice', home_address: { zip_code: '12345' } }
1170
+ * @since 3.0.3
1171
+ */
1172
+ function snakeCaseKeys(value) {
1173
+ return mapDeep(value, void 0, snakeCase);
1174
+ }
1175
+ //#endregion
1176
+ //#region helpers/object/sortKeys.ts
1177
+ /**
1178
+ * This file is part of helpers4.
1179
+ * Copyright (C) 2025 baxyz
1180
+ * SPDX-License-Identifier: LGPL-3.0-or-later
1181
+ */
1182
+ /**
1183
+ * Creates a new object with the same entries as the input, but with its own keys sorted.
1184
+ * Shallow only — nested objects are copied as-is, their keys are not re-sorted.
1185
+ * Key order matters for things like `JSON.stringify` diffs and snapshot stability; this does
1186
+ * not change equality (`{a:1,b:2}` and `{b:2,a:1}` are already equal), just iteration/serialization order.
1187
+ * Note: integer-index-like keys (`"0"`, `"1"`, `"42"`...) are always iterated first, in numeric
1188
+ * order, by the JS engine itself — no object key ordering can override that language behavior.
1189
+ * A prototype-polluting key (`__proto__`, `constructor`, `prototype`) is silently skipped,
1190
+ * same as the rest of `@helpers4/object`.
1191
+ * @param obj - The object whose keys to sort
1192
+ * @param compareFn - Optional custom comparator, passed directly to `Array.prototype.sort`
1193
+ * @returns A new object with the same entries, own keys in sorted order
1194
+ * @example
1195
+ * sortKeys({ b: 2, a: 1, c: 3 })
1196
+ * // => { a: 1, b: 2, c: 3 }
1197
+ * @since 3.0.3
1198
+ */
1199
+ function sortKeys(obj, compareFn) {
1200
+ const sortedKeys = Object.keys(obj).toSorted(compareFn);
1201
+ const result = {};
1202
+ for (const key of sortedKeys) {
1203
+ if (UNSAFE_KEYS.has(key)) continue;
1204
+ result[key] = obj[key];
1205
+ }
1206
+ return result;
1207
+ }
1208
+ //#endregion
1209
+ //#region helpers/string/titleCase.ts
1210
+ function titleCase(str) {
1211
+ if (str === void 0 || str === null) return str;
1212
+ return str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").split(/[\s_-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
1213
+ }
1214
+ //#endregion
1215
+ //#region helpers/object/titleCaseKeys.ts
1216
+ /**
1217
+ * This file is part of helpers4.
1218
+ * Copyright (C) 2025 baxyz
1219
+ * SPDX-License-Identifier: LGPL-3.0-or-later
1220
+ */
1221
+ /**
1222
+ * Recursively transforms every key of a plain object (including keys nested inside arrays and
1223
+ * nested objects) to Title Case. Handles camelCase, snake_case, kebab-case, and space-separated
1224
+ * keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
1225
+ * untouched, only their position is walked into. An entry whose transformed key is a
1226
+ * prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
1227
+ * same as the rest of `@helpers4/object`.
1228
+ *
1229
+ * Note: unlike the other `*CaseKeys` variants, `titleCase` joins words with spaces (e.g.
1230
+ * `'User Name'`) — those keys are only usable via bracket access (`obj['User Name']`), not dot
1231
+ * notation. Useful for display-facing structures (form labels, table headers), not wire formats.
1232
+ * @param value - The object (or array of objects) to transform
1233
+ * @returns A new value with every plain-object key converted to Title Case
1234
+ * @example
1235
+ * titleCaseKeys({ user_name: 'Alice', home_address: { zip_code: '12345' } })
1236
+ * // => { 'User Name': 'Alice', 'Home Address': { 'Zip Code': '12345' } }
1237
+ * @since 3.0.3
1238
+ */
1239
+ function titleCaseKeys(value) {
1240
+ return mapDeep(value, void 0, titleCase);
1241
+ }
1242
+ //#endregion
974
1243
  //#region helpers/object/unflatten.ts
975
1244
  /**
976
1245
  * This file is part of helpers4.
@@ -1050,6 +1319,6 @@ function update(obj, path, updater) {
1050
1319
  return set(obj, path, updater(get(obj, path)));
1051
1320
  }
1052
1321
  //#endregion
1053
- export { clone, cloneDeep, compact, diff, equalsDeep, equalsShallow, flatten, get, groupBy, invert, isEmpty, isNonEmpty, map, mergeDeep, omit, omitBy, parsePropertyPath, pick, pickBy, removeUndefinedNull, safeJsonParse, set, unflatten, unset, update };
1322
+ export { camelCaseKeys, clone, cloneDeep, compact, diff, equalsDeep, equalsShallow, flatten, get, groupBy, invert, isEmpty, isNonEmpty, kebabCaseKeys, map, mapDeep, mergeDeep, omit, omitBy, parsePropertyPath, pascalCaseKeys, pick, pickBy, removeUndefinedNull, safeJsonParse, set, snakeCaseKeys, sortKeys, titleCaseKeys, unflatten, unset, update };
1054
1323
 
1055
1324
  //# sourceMappingURL=index.js.map