@helpers4/object 2.0.3 → 2.1.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/README.md +2 -2
- package/lib/index.d.ts +295 -50
- package/lib/index.js +163 -76
- package/lib/index.js.map +1 -1
- package/llms.txt +239 -122
- package/meta/api.json +207 -169
- package/meta/examples.json +38 -23
- package/meta/native-alternatives.json +1 -1
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -1,37 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return result;
|
|
7
|
-
}
|
|
1
|
+
var UNSAFE_KEYS = new Set([
|
|
2
|
+
"__proto__",
|
|
3
|
+
"constructor",
|
|
4
|
+
"prototype"
|
|
5
|
+
]);
|
|
8
6
|
//#endregion
|
|
9
|
-
//#region helpers/object/
|
|
7
|
+
//#region helpers/object/cloneDeep.ts
|
|
10
8
|
/**
|
|
11
9
|
* This file is part of helpers4.
|
|
12
10
|
* Copyright (C) 2025 baxyz
|
|
13
11
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
14
12
|
*/
|
|
15
|
-
var UNSAFE_KEYS$2 = new Set([
|
|
16
|
-
"__proto__",
|
|
17
|
-
"constructor",
|
|
18
|
-
"prototype"
|
|
19
|
-
]);
|
|
20
13
|
/**
|
|
21
|
-
* Creates a deep copy of an object or array
|
|
22
|
-
*
|
|
23
|
-
*
|
|
14
|
+
* Creates a deep copy of an object or array.
|
|
15
|
+
*
|
|
16
|
+
* Recursively clones own enumerable string keys. `Date` instances are
|
|
17
|
+
* reconstructed with the same epoch value. Prototype-polluting keys
|
|
18
|
+
* (`__proto__`, `constructor`, `prototype`) are silently skipped.
|
|
19
|
+
* Does **not** handle circular references.
|
|
20
|
+
*
|
|
21
|
+
* **Limitation:** symbol-keyed own properties are **not** copied — only string
|
|
22
|
+
* keys are processed. Use `structuredClone` if symbol propagation is required.
|
|
23
|
+
*
|
|
24
|
+
* @param obj - The value to clone
|
|
25
|
+
* @returns A deep copy of `obj`
|
|
26
|
+
* @example
|
|
27
|
+
* cloneDeep({ a: { b: [1, 2] } })
|
|
28
|
+
* // => { a: { b: [1, 2] } } — fully independent copy
|
|
29
|
+
*
|
|
30
|
+
* cloneDeep(new Date('2024-01-01'))
|
|
31
|
+
* // => Date with the same timestamp
|
|
32
|
+
*
|
|
33
|
+
* // Primitives pass through unchanged
|
|
34
|
+
* cloneDeep(42) // => 42
|
|
35
|
+
* cloneDeep(null) // => null
|
|
24
36
|
* @since 1.9.0
|
|
25
37
|
*/
|
|
26
|
-
function
|
|
38
|
+
function cloneDeep(obj) {
|
|
27
39
|
if (obj === null || typeof obj !== "object") return obj;
|
|
28
40
|
if (obj instanceof Date) return new Date(obj.getTime());
|
|
29
|
-
if (Array.isArray(obj)) return obj.map((item) =>
|
|
41
|
+
if (Array.isArray(obj)) return obj.map((item) => cloneDeep(item));
|
|
30
42
|
const cloned = {};
|
|
31
|
-
for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key) && !UNSAFE_KEYS
|
|
43
|
+
for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key) && !UNSAFE_KEYS.has(key)) cloned[key] = cloneDeep(obj[key]);
|
|
32
44
|
return cloned;
|
|
33
45
|
}
|
|
34
46
|
//#endregion
|
|
47
|
+
//#region helpers/object/compact.ts
|
|
48
|
+
function compact(obj) {
|
|
49
|
+
if (obj === void 0 || obj === null) return obj;
|
|
50
|
+
const result = {};
|
|
51
|
+
for (const key of Object.keys(obj)) if (obj[key]) result[key] = obj[key];
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
35
55
|
//#region helpers/type/isPlainObject.ts
|
|
36
56
|
/**
|
|
37
57
|
* This file is part of helpers4.
|
|
@@ -60,32 +80,6 @@ function isPlainObject(value) {
|
|
|
60
80
|
return proto === Object.prototype || proto === null;
|
|
61
81
|
}
|
|
62
82
|
//#endregion
|
|
63
|
-
//#region helpers/object/deepMerge.ts
|
|
64
|
-
/**
|
|
65
|
-
* This file is part of helpers4.
|
|
66
|
-
* Copyright (C) 2025 baxyz
|
|
67
|
-
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
68
|
-
*/
|
|
69
|
-
var UNSAFE_KEYS$1 = new Set([
|
|
70
|
-
"__proto__",
|
|
71
|
-
"constructor",
|
|
72
|
-
"prototype"
|
|
73
|
-
]);
|
|
74
|
-
function deepMerge(target, ...sources) {
|
|
75
|
-
if (target === void 0 || target === null) return target;
|
|
76
|
-
if (!sources.length) return target;
|
|
77
|
-
const source = sources.shift();
|
|
78
|
-
if (!source) return deepMerge(target, ...sources);
|
|
79
|
-
for (const key in source) {
|
|
80
|
-
if (!Object.hasOwn(source, key) || UNSAFE_KEYS$1.has(key)) continue;
|
|
81
|
-
const targetValue = target[key];
|
|
82
|
-
const sourceValue = source[key];
|
|
83
|
-
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) target[key] = deepMerge(targetValue, sourceValue);
|
|
84
|
-
else if (sourceValue !== void 0) target[key] = sourceValue;
|
|
85
|
-
}
|
|
86
|
-
return deepMerge(target, ...sources);
|
|
87
|
-
}
|
|
88
|
-
//#endregion
|
|
89
83
|
//#region helpers/array/equalsDeep.ts
|
|
90
84
|
/**
|
|
91
85
|
* This file is part of helpers4.
|
|
@@ -443,22 +437,71 @@ function equalsShallow(objA, objB) {
|
|
|
443
437
|
return true;
|
|
444
438
|
}
|
|
445
439
|
//#endregion
|
|
446
|
-
//#region helpers/object/
|
|
440
|
+
//#region helpers/object/_parsePath.ts
|
|
447
441
|
/**
|
|
448
442
|
* This file is part of helpers4.
|
|
449
443
|
* Copyright (C) 2025 baxyz
|
|
450
444
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
451
445
|
*/
|
|
452
446
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
447
|
+
* Parses a string path into an array of string/number segments.
|
|
448
|
+
*
|
|
449
|
+
* - Dot separators (`.`) split segments; each segment becomes a string key.
|
|
450
|
+
* - Bracket indices (`[n]`) become number keys.
|
|
451
|
+
* - A leading `.` is treated as "current level" and stripped before parsing,
|
|
452
|
+
* so `.[0]` ≡ `[0]` and `.a.b` ≡ `a.b`.
|
|
453
|
+
* - Empty string (or bare `.`) returns `['']` (addresses the `''` key on the root object).
|
|
454
|
+
* - Consecutive dots (`a..b`) produce an empty-string segment: `['a', '', 'b']`.
|
|
455
|
+
* - Non-bracket text trailing a `]` within the same dot-segment throws a RangeError;
|
|
456
|
+
* use a dot separator instead (`'a[0].b'` not `'a[0]b'`).
|
|
457
|
+
*
|
|
458
|
+
* @ignore
|
|
459
|
+
* @example
|
|
460
|
+
* parsePath('a.b.c') // => ['a', 'b', 'c'] (all strings)
|
|
461
|
+
* parsePath('layers.1.x') // => ['layers', '1', 'x'] (all strings)
|
|
462
|
+
* parsePath('layers[1].x') // => ['layers', 1, 'x'] (bracket → number)
|
|
463
|
+
* parsePath('.[0]') // => [0] (leading dot stripped)
|
|
464
|
+
* parsePath('a..b') // => ['a', '', 'b'] (empty segment preserved)
|
|
465
|
+
* parsePath('.') // => [''] (leading dot → empty-string key)
|
|
466
|
+
*/
|
|
467
|
+
var BRACKET = /\[(\d+)\]/g;
|
|
468
|
+
var parsePathCache = /* @__PURE__ */ new Map();
|
|
469
|
+
function parsePath(path) {
|
|
470
|
+
const cached = parsePathCache.get(path);
|
|
471
|
+
if (cached !== void 0) return cached;
|
|
472
|
+
const result = Object.freeze(parsePathUncached(path));
|
|
473
|
+
parsePathCache.set(path, result);
|
|
474
|
+
return result;
|
|
475
|
+
}
|
|
476
|
+
function parsePathUncached(path) {
|
|
477
|
+
if (path === "") return [""];
|
|
478
|
+
if (path[0] === ".") path = path.slice(1);
|
|
479
|
+
if (path === "") return [""];
|
|
480
|
+
const result = [];
|
|
481
|
+
for (const dotSegment of path.split(".")) {
|
|
482
|
+
BRACKET.lastIndex = 0;
|
|
483
|
+
let last = 0;
|
|
484
|
+
let match;
|
|
485
|
+
while ((match = BRACKET.exec(dotSegment)) !== null) {
|
|
486
|
+
if (match.index > last) result.push(dotSegment.slice(last, match.index));
|
|
487
|
+
result.push(parseInt(match[1], 10));
|
|
488
|
+
last = BRACKET.lastIndex;
|
|
489
|
+
}
|
|
490
|
+
if (last === 0) result.push(dotSegment);
|
|
491
|
+
else if (last < dotSegment.length) throw new RangeError(`parsePath: non-bracket text '${dotSegment.slice(last)}' after ']' in segment '${dotSegment}' — use 'a[0].b' instead of 'a[0]b'`);
|
|
492
|
+
}
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region helpers/object/get.ts
|
|
497
|
+
/**
|
|
498
|
+
* This file is part of helpers4.
|
|
499
|
+
* Copyright (C) 2025 baxyz
|
|
500
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
459
501
|
*/
|
|
460
502
|
function get(obj, path, defaultValue) {
|
|
461
|
-
|
|
503
|
+
if (obj == null) return defaultValue;
|
|
504
|
+
const keys = typeof path === "string" ? parsePath(path) : path;
|
|
462
505
|
let result = obj;
|
|
463
506
|
for (const key of keys) {
|
|
464
507
|
if (result == null || typeof result !== "object") return defaultValue;
|
|
@@ -478,6 +521,9 @@ function get(obj, path, defaultValue) {
|
|
|
478
521
|
*
|
|
479
522
|
* A thin, typed wrapper around `Object.groupBy` (ES2024) that works on
|
|
480
523
|
* older targets and provides stricter return-type inference.
|
|
524
|
+
* `null` and `undefined` are treated as empty arrays and return `{}`.
|
|
525
|
+
* Items whose computed key is a prototype-polluting string (`__proto__`,
|
|
526
|
+
* `constructor`, `prototype`) are silently skipped.
|
|
481
527
|
*
|
|
482
528
|
* @param items - The array to group
|
|
483
529
|
* @param keyFn - Function that returns the group key for each item
|
|
@@ -485,12 +531,16 @@ function get(obj, path, defaultValue) {
|
|
|
485
531
|
* @example
|
|
486
532
|
* groupBy([1, 2, 3, 4], n => n % 2 === 0 ? 'even' : 'odd')
|
|
487
533
|
* // => { odd: [1, 3], even: [2, 4] }
|
|
534
|
+
* groupBy(null, n => n % 2 === 0 ? 'even' : 'odd')
|
|
535
|
+
* // => {}
|
|
488
536
|
* @since 2.0.0
|
|
489
537
|
*/
|
|
490
538
|
function groupBy(items, keyFn) {
|
|
491
539
|
const result = {};
|
|
540
|
+
if (items == null) return result;
|
|
492
541
|
for (const item of items) {
|
|
493
542
|
const key = keyFn(item);
|
|
543
|
+
if (typeof key === "string" && UNSAFE_KEYS.has(key)) continue;
|
|
494
544
|
if (result[key] === void 0) result[key] = [];
|
|
495
545
|
result[key].push(item);
|
|
496
546
|
}
|
|
@@ -506,6 +556,9 @@ function groupBy(items, keyFn) {
|
|
|
506
556
|
/**
|
|
507
557
|
* Returns a new object with keys and values swapped.
|
|
508
558
|
* If multiple keys share the same value, the last one wins.
|
|
559
|
+
* `null` and `undefined` are treated as empty objects and return `{}`.
|
|
560
|
+
* Entries whose source key **or** value is a prototype-polluting string
|
|
561
|
+
* (`__proto__`, `constructor`, `prototype`) are silently skipped.
|
|
509
562
|
*
|
|
510
563
|
* @param obj - The object whose keys and values are to be swapped
|
|
511
564
|
* @returns A new object with values as keys and original keys as values
|
|
@@ -515,11 +568,19 @@ function groupBy(items, keyFn) {
|
|
|
515
568
|
*
|
|
516
569
|
* invert({ one: 1, two: 2 })
|
|
517
570
|
* // => { 1: 'one', 2: 'two' }
|
|
571
|
+
*
|
|
572
|
+
* invert(null)
|
|
573
|
+
* // => {}
|
|
518
574
|
* @since 2.0.0
|
|
519
575
|
*/
|
|
520
576
|
function invert(obj) {
|
|
521
577
|
const result = {};
|
|
522
|
-
|
|
578
|
+
if (obj == null) return result;
|
|
579
|
+
for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key) && !UNSAFE_KEYS.has(key)) {
|
|
580
|
+
const val = obj[key];
|
|
581
|
+
if (typeof val === "string" && UNSAFE_KEYS.has(val)) continue;
|
|
582
|
+
result[val] = key;
|
|
583
|
+
}
|
|
523
584
|
return result;
|
|
524
585
|
}
|
|
525
586
|
//#endregion
|
|
@@ -531,20 +592,23 @@ function invert(obj) {
|
|
|
531
592
|
*/
|
|
532
593
|
/**
|
|
533
594
|
* Checks if a plain object has no own enumerable string-keyed properties.
|
|
595
|
+
* `null` and `undefined` are treated as empty objects and return `true`.
|
|
534
596
|
*
|
|
535
597
|
* Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
|
|
536
598
|
* separately if symbol keys matter for your use case.
|
|
537
599
|
*
|
|
538
600
|
* @param value - The object to check
|
|
539
|
-
* @returns `true` if the object has no own enumerable string-keyed properties
|
|
601
|
+
* @returns `true` if the object has no own enumerable string-keyed properties, or if `value` is `null`/`undefined`
|
|
540
602
|
* @example
|
|
541
603
|
* isEmpty({}) // => true
|
|
604
|
+
* isEmpty(null) // => true
|
|
605
|
+
* isEmpty(undefined) // => true
|
|
542
606
|
* isEmpty({ a: 1 }) // => false
|
|
543
607
|
* isEmpty({ a: undefined }) // => false (key exists)
|
|
544
608
|
* @since 2.0.3
|
|
545
609
|
*/
|
|
546
610
|
function isEmpty(value) {
|
|
547
|
-
return Object.keys(value).length === 0;
|
|
611
|
+
return value == null || Object.keys(value).length === 0;
|
|
548
612
|
}
|
|
549
613
|
//#endregion
|
|
550
614
|
//#region helpers/object/isNonEmpty.ts
|
|
@@ -555,19 +619,23 @@ function isEmpty(value) {
|
|
|
555
619
|
*/
|
|
556
620
|
/**
|
|
557
621
|
* Checks if a plain object has at least one own enumerable string-keyed property.
|
|
622
|
+
* `null` and `undefined` are treated as empty objects and return `false`.
|
|
558
623
|
*
|
|
559
624
|
* Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
|
|
560
625
|
* separately if symbol keys matter for your use case.
|
|
561
626
|
*
|
|
562
627
|
* @param value - The object to check
|
|
563
|
-
* @returns `true` if the object has at least one own enumerable string-keyed property
|
|
628
|
+
* @returns `true` if the object has at least one own enumerable string-keyed property; `false` for empty, `null`, or `undefined`.
|
|
629
|
+
* Acts as a type guard: the `if` branch narrows `Record<PropertyKey, unknown> | null | undefined` to `Record<PropertyKey, unknown>`.
|
|
564
630
|
* @example
|
|
565
631
|
* isNonEmpty({ a: 1 }) // => true
|
|
566
632
|
* isNonEmpty({}) // => false
|
|
633
|
+
* isNonEmpty(null) // => false
|
|
634
|
+
* isNonEmpty(undefined) // => false
|
|
567
635
|
* @since 2.0.3
|
|
568
636
|
*/
|
|
569
637
|
function isNonEmpty(value) {
|
|
570
|
-
return Object.keys(value).length > 0;
|
|
638
|
+
return value != null && Object.keys(value).length > 0;
|
|
571
639
|
}
|
|
572
640
|
//#endregion
|
|
573
641
|
//#region helpers/object/map.ts
|
|
@@ -584,7 +652,8 @@ function isNonEmpty(value) {
|
|
|
584
652
|
* when `mapKey` is omitted the original keys are preserved.
|
|
585
653
|
*
|
|
586
654
|
* Note: if two different keys map to the same output key the last one wins
|
|
587
|
-
* (insertion order).
|
|
655
|
+
* (insertion order). Entries whose mapped key is a prototype-polluting string
|
|
656
|
+
* (`__proto__`, `constructor`, `prototype`) are silently skipped.
|
|
588
657
|
*
|
|
589
658
|
* @param obj - The source object
|
|
590
659
|
* @param mapValue - Callback called with `(value, key)` for each entry.
|
|
@@ -605,14 +674,44 @@ function isNonEmpty(value) {
|
|
|
605
674
|
*/
|
|
606
675
|
function map(obj, mapValue, mapKey) {
|
|
607
676
|
const result = {};
|
|
677
|
+
if (obj == null) return result;
|
|
608
678
|
for (const key of Object.keys(obj)) {
|
|
609
679
|
const value = obj[key];
|
|
610
680
|
const newKey = mapKey ? mapKey(key, value) : key;
|
|
681
|
+
if (typeof newKey === "string" && UNSAFE_KEYS.has(newKey)) continue;
|
|
611
682
|
result[newKey] = mapValue ? mapValue(value, key) : value;
|
|
612
683
|
}
|
|
613
684
|
return result;
|
|
614
685
|
}
|
|
615
686
|
//#endregion
|
|
687
|
+
//#region helpers/object/mergeDeep.ts
|
|
688
|
+
/**
|
|
689
|
+
* This file is part of helpers4.
|
|
690
|
+
* Copyright (C) 2025 baxyz
|
|
691
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
692
|
+
*/
|
|
693
|
+
function applyKey(result, source, key) {
|
|
694
|
+
const resultValue = result[key];
|
|
695
|
+
const sourceValue = source[key];
|
|
696
|
+
if (isPlainObject(resultValue) && isPlainObject(sourceValue)) result[key] = mergeDeep(resultValue, sourceValue);
|
|
697
|
+
else if (sourceValue !== void 0) result[key] = isPlainObject(sourceValue) || Array.isArray(sourceValue) ? cloneDeep(sourceValue) : sourceValue;
|
|
698
|
+
}
|
|
699
|
+
function applySource(result, source) {
|
|
700
|
+
for (const key in source) {
|
|
701
|
+
if (!Object.hasOwn(source, key) || UNSAFE_KEYS.has(key)) continue;
|
|
702
|
+
applyKey(result, source, key);
|
|
703
|
+
}
|
|
704
|
+
for (const sym of Object.getOwnPropertySymbols(source)) {
|
|
705
|
+
if (!Object.prototype.propertyIsEnumerable.call(source, sym)) continue;
|
|
706
|
+
applyKey(result, source, sym);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function mergeDeep(...sources) {
|
|
710
|
+
const result = {};
|
|
711
|
+
for (const source of sources) if (source !== null && source !== void 0) applySource(result, source);
|
|
712
|
+
return result;
|
|
713
|
+
}
|
|
714
|
+
//#endregion
|
|
616
715
|
//#region helpers/object/omit.ts
|
|
617
716
|
function omit(obj, keys) {
|
|
618
717
|
if (obj === void 0 || obj === null) return obj;
|
|
@@ -688,32 +787,20 @@ function safeJsonParse(input, fallback) {
|
|
|
688
787
|
* Copyright (C) 2025 baxyz
|
|
689
788
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
690
789
|
*/
|
|
691
|
-
var UNSAFE_KEYS = new Set([
|
|
692
|
-
"__proto__",
|
|
693
|
-
"constructor",
|
|
694
|
-
"prototype"
|
|
695
|
-
]);
|
|
696
|
-
/**
|
|
697
|
-
* Sets a value in an object using a dot-notated path
|
|
698
|
-
* @param obj - The object to set value in
|
|
699
|
-
* @param path - The dot-notated path (e.g., 'a.b.c')
|
|
700
|
-
* @param value - The value to set
|
|
701
|
-
* @returns The modified object
|
|
702
|
-
* @since 1.9.0
|
|
703
|
-
*/
|
|
704
790
|
function set(obj, path, value) {
|
|
705
|
-
const keys = path
|
|
706
|
-
if (keys.
|
|
791
|
+
const keys = typeof path === "string" ? parsePath(path) : path;
|
|
792
|
+
if (keys.length === 0) return obj;
|
|
793
|
+
if (keys.some((k) => typeof k === "string" && UNSAFE_KEYS.has(k))) return obj;
|
|
707
794
|
let current = obj;
|
|
708
795
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
709
796
|
const key = keys[i];
|
|
710
|
-
if (!(key
|
|
797
|
+
if (!Object.hasOwn(current, key) || typeof current[key] !== "object" || current[key] === null) current[key] = {};
|
|
711
798
|
current = current[key];
|
|
712
799
|
}
|
|
713
800
|
current[keys[keys.length - 1]] = value;
|
|
714
801
|
return obj;
|
|
715
802
|
}
|
|
716
803
|
//#endregion
|
|
717
|
-
export {
|
|
804
|
+
export { cloneDeep, cloneDeep as deepClone, compact, mergeDeep as deepMerge, mergeDeep, diff, equalsDeep, equalsShallow, get, groupBy, invert, isEmpty, isNonEmpty, map, omit, pick, removeUndefinedNull, safeJsonParse, set };
|
|
718
805
|
|
|
719
806
|
//# sourceMappingURL=index.js.map
|