js-powerkit 1.0.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/LICENSE +21 -0
- package/README.md +519 -0
- package/package.json +48 -0
- package/src/arrayUtils.js +347 -0
- package/src/index.js +8 -0
- package/src/objectUtils.js +423 -0
- package/src/stringUtils.js +319 -0
- package/tests/arrayUtils.test.js +204 -0
- package/tests/objectUtils.test.js +211 -0
- package/tests/stringUtils.test.js +220 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object Utility Functions
|
|
3
|
+
* @module object
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Deep clones an object.
|
|
9
|
+
* @param {Object} obj - The object to clone.
|
|
10
|
+
* @returns {Object} The cloned object.
|
|
11
|
+
* @example
|
|
12
|
+
* deepClone({a: 1, b: {c: 2}}); // {a: 1, b: {c: 2}}
|
|
13
|
+
*/
|
|
14
|
+
export const deepClone = (obj) => {
|
|
15
|
+
if (obj === null || typeof obj !== 'object') return obj;
|
|
16
|
+
if (obj instanceof Date) return new Date(obj.getTime());
|
|
17
|
+
if (obj instanceof Array) return obj.map(item => deepClone(item));
|
|
18
|
+
if (typeof obj === 'object') {
|
|
19
|
+
const cloned = {};
|
|
20
|
+
Object.keys(obj).forEach(key => {
|
|
21
|
+
cloned[key] = deepClone(obj[key]);
|
|
22
|
+
});
|
|
23
|
+
return cloned;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Deep merges two or more objects
|
|
29
|
+
* @param {...Object} objects - The objects to merge.
|
|
30
|
+
* @returns {Object} The merged object.
|
|
31
|
+
* @example
|
|
32
|
+
* deepMerge({a: 1}, {b: 2}); // {a: 1, b: 2}
|
|
33
|
+
*/
|
|
34
|
+
export const deepMerge = (...objects) => {
|
|
35
|
+
return objects.reduce((merged, obj) => {
|
|
36
|
+
if (obj && typeof obj === 'object') {
|
|
37
|
+
Object.keys(obj).forEach(key => {
|
|
38
|
+
if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
|
39
|
+
merged[key] = deepMerge(merged[key] || {}, obj[key]);
|
|
40
|
+
} else {
|
|
41
|
+
merged[key] = obj[key];
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return merged;
|
|
46
|
+
}, {});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Picks specified properties from an object.
|
|
51
|
+
* @param {Object} obj - The source object.
|
|
52
|
+
* @param {Array} keys - The keys to pick.
|
|
53
|
+
* @returns {Object} The object with picked properties.
|
|
54
|
+
* @example
|
|
55
|
+
* pick({a: 1, b: 2, c: 3}, ['a', 'c']); // {a: 1, c: 3}
|
|
56
|
+
*/
|
|
57
|
+
export const pick = (obj, keys) => {
|
|
58
|
+
if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
|
|
59
|
+
if (!Array.isArray(keys)) throw new TypeError('Second argument must be an array');
|
|
60
|
+
const result = {};
|
|
61
|
+
keys.forEach(key => {
|
|
62
|
+
if (key in obj) result[key] = obj[key];
|
|
63
|
+
});
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Omits specified properties from an object.
|
|
69
|
+
* @param {Object} obj - The source object.
|
|
70
|
+
* @param {Array} keys - The keys to omit.
|
|
71
|
+
* @returns {Object} The object without omitted properties.
|
|
72
|
+
* @example
|
|
73
|
+
* omit({a: 1, b: 2, c: 3}, ['b']); // {a: 1, c: 3}
|
|
74
|
+
*/
|
|
75
|
+
export const omit = (obj, keys) => {
|
|
76
|
+
if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
|
|
77
|
+
if (!Array.isArray(keys)) throw new TypeError('Second argument must be an array');
|
|
78
|
+
const result = { ...obj };
|
|
79
|
+
keys.forEach(key => delete result[key]);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Checks if an object is empty.
|
|
85
|
+
* @param {Object} obj - The object to check.
|
|
86
|
+
* @returns {boolean} True if the object is empty.
|
|
87
|
+
* @example
|
|
88
|
+
* isEmpty({}); // true
|
|
89
|
+
* isEmpty({a: 1}); // false
|
|
90
|
+
*/
|
|
91
|
+
export const isEmpty = (obj) => {
|
|
92
|
+
return obj != null && typeof obj === 'object' && Object.keys(obj).length === 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Inverts the keys and values of an object.
|
|
97
|
+
* @param {Object} obj - The object to invert.
|
|
98
|
+
* @returns {Object} The inverted object.
|
|
99
|
+
* @example
|
|
100
|
+
* invert({a: 1, b: 2}); // {1: 'a', 2: 'b'}
|
|
101
|
+
*/
|
|
102
|
+
export const invert = (obj) => {
|
|
103
|
+
if (!obj || typeof obj !== 'object') throw new TypeError('Input must be an object');
|
|
104
|
+
const result = {};
|
|
105
|
+
Object.keys(obj).forEach(key => {
|
|
106
|
+
result[obj[key]] = key;
|
|
107
|
+
});
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Maps the keys of an object using a function.
|
|
113
|
+
* @param {Object} obj - The source object.
|
|
114
|
+
* @param {Function} fn - The mapping function.
|
|
115
|
+
* @returns {Object} The object with mapped keys.
|
|
116
|
+
* @example
|
|
117
|
+
* mapKeys({a: 1, b: 2}, key => key.toUpperCase()); // {A: 1, B: 2}
|
|
118
|
+
*/
|
|
119
|
+
export const mapKeys = (obj, fn) => {
|
|
120
|
+
if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
|
|
121
|
+
if (typeof fn !== 'function') throw new TypeError('Second argument must be a function');
|
|
122
|
+
const result = {};
|
|
123
|
+
Object.keys(obj).forEach(key => {
|
|
124
|
+
result[fn(key)] = obj[key];
|
|
125
|
+
});
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Maps the values of an object using a function.
|
|
131
|
+
* @param {Object} obj - The source object.
|
|
132
|
+
* @param {Function} fn - The mapping function.
|
|
133
|
+
* @returns {Object} The object with mapped values.
|
|
134
|
+
* @example
|
|
135
|
+
* mapValues({a: 1, b: 2}, val => val * 2); // {a: 2, b: 4}
|
|
136
|
+
*/
|
|
137
|
+
export const mapValues = (obj, fn) => {
|
|
138
|
+
if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
|
|
139
|
+
if (typeof fn !== 'function') throw new TypeError('Second argument must be a function');
|
|
140
|
+
const result = {};
|
|
141
|
+
Object.keys(obj).forEach(key => {
|
|
142
|
+
result[key] = fn(obj[key]);
|
|
143
|
+
});
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Sets default values for an object.
|
|
149
|
+
* @param {Object} obj - The target object.
|
|
150
|
+
* @param {Object} defaults - The default values.
|
|
151
|
+
* @returns {Object} The object with defaults applied.
|
|
152
|
+
* @example
|
|
153
|
+
* defaults({a: 1}, {a: 2, b: 3}); // {a: 1, b: 3}
|
|
154
|
+
*/
|
|
155
|
+
export const defaults = (obj, defaults) => {
|
|
156
|
+
if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
|
|
157
|
+
if (!defaults || typeof defaults !== 'object') throw new TypeError('Second argument must be an object');
|
|
158
|
+
const result = { ...defaults };
|
|
159
|
+
Object.keys(obj).forEach(key => {
|
|
160
|
+
if (obj[key] !== undefined) result[key] = obj[key];
|
|
161
|
+
});
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Gets a nested property value using dot notation
|
|
167
|
+
* @param {Object} obj - The object
|
|
168
|
+
* @param {string} path - Path to property (e.g., 'user.address.city')
|
|
169
|
+
* @param {*} defaultValue - Default value if not found
|
|
170
|
+
* @returns {*} Property value
|
|
171
|
+
* @example
|
|
172
|
+
* getNestedValue({ a: { b: { c: 42 } } }, 'a.b.c'); // 42
|
|
173
|
+
* getNestedValue({ a: { b: { c: 42 } } }, 'a.c', 'default'); // 'default'
|
|
174
|
+
*/
|
|
175
|
+
export const getNestedValue = (obj, path, defaultValue = undefined) => {
|
|
176
|
+
if (!obj || typeof path !== 'string') return defaultValue;
|
|
177
|
+
|
|
178
|
+
const keys = path.split('.');
|
|
179
|
+
let result = obj;
|
|
180
|
+
|
|
181
|
+
for (const key of keys) {
|
|
182
|
+
result = (result && result[key]) ? result[key] : defaultValue;
|
|
183
|
+
if (result === undefined) return defaultValue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Sets a nested property value using dot notation
|
|
191
|
+
* @param {Object} obj - The object
|
|
192
|
+
* @param {string} path - Path to property
|
|
193
|
+
* @param {*} value - Value to set
|
|
194
|
+
* @returns {Object} Modified object
|
|
195
|
+
* @example
|
|
196
|
+
* let obj = { a: { b: 1 } };
|
|
197
|
+
* setNestedValue(obj, 'a.c.d', 42);
|
|
198
|
+
* console.log(obj.a.c.d); // 42
|
|
199
|
+
*/
|
|
200
|
+
export const setNestedValue = (obj, path, value) => {
|
|
201
|
+
if (!obj || typeof path !== 'string') return obj;
|
|
202
|
+
|
|
203
|
+
const keys = path.split('.');
|
|
204
|
+
const lastKey = keys.pop();
|
|
205
|
+
let current = obj;
|
|
206
|
+
|
|
207
|
+
for (const key of keys) {
|
|
208
|
+
if (!current[key] || typeof current[key] !== 'object') {
|
|
209
|
+
current[key] = {};
|
|
210
|
+
}
|
|
211
|
+
current = current[key];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
current[lastKey] = value;
|
|
215
|
+
return obj;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Gets all keys from nested object (flattened)
|
|
220
|
+
* @param {Object} obj - The object
|
|
221
|
+
* @param {string} prefix - Prefix for keys
|
|
222
|
+
* @returns {Array<string>} Array of all keys
|
|
223
|
+
* @example
|
|
224
|
+
* getAllKeys({ a: 1, b: { c: 2, d: { e: 3 } } }); // ['a', 'b.c', 'b.d.e']
|
|
225
|
+
*/
|
|
226
|
+
export const getAllKeys = (obj, prefix = '') => {
|
|
227
|
+
if (!obj || typeof obj !== 'object') return [];
|
|
228
|
+
|
|
229
|
+
return Object.keys(obj).reduce((keys, key) => {
|
|
230
|
+
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
231
|
+
|
|
232
|
+
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
233
|
+
return keys.concat(getAllKeys(obj[key], newKey));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return keys.concat(newKey);
|
|
237
|
+
}, []);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Flattens a nested object
|
|
242
|
+
* @param {Object} obj - The object to flatten
|
|
243
|
+
* @param {string} prefix - Prefix for keys
|
|
244
|
+
* @returns {Object} Flattened object
|
|
245
|
+
* @example
|
|
246
|
+
* flattenObject({ a: 1, b: { c: 2, d: { e: 3 } } }); // {'a': 1, 'b.c': 2, 'b.d.e': 3 }
|
|
247
|
+
*/
|
|
248
|
+
export const flattenObject = (obj, prefix = '') => {
|
|
249
|
+
if (!obj || typeof obj !== 'object') return {};
|
|
250
|
+
|
|
251
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
252
|
+
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
253
|
+
|
|
254
|
+
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
255
|
+
Object.assign(acc, flattenObject(obj[key], newKey));
|
|
256
|
+
} else {
|
|
257
|
+
acc[newKey] = obj[key];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return acc;
|
|
261
|
+
}, {});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Unflattens a flattened object
|
|
266
|
+
* @param {Object} obj - The flattened object
|
|
267
|
+
* @returns {Object} Nested object
|
|
268
|
+
* @example
|
|
269
|
+
* unflattenObject({ 'a': 1, 'b.c': 2, 'b.d.e': 3 }); // { a: 1, b: { c: 2, d: { e: 3 } } }
|
|
270
|
+
*/
|
|
271
|
+
export const unflattenObject = (obj) => {
|
|
272
|
+
if (!obj || typeof obj !== 'object') return {};
|
|
273
|
+
|
|
274
|
+
const result = {};
|
|
275
|
+
|
|
276
|
+
for (const key in obj) {
|
|
277
|
+
setNestedValue(result, key, obj[key]);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Removes null and undefined values from object
|
|
285
|
+
* @param {Object} obj - The object
|
|
286
|
+
* @returns {Object} Object without null/undefined values
|
|
287
|
+
* @example
|
|
288
|
+
* removeNullish({ a: 1, b: null, c: undefined, d: 0, e: '' }); // { a: 1, d: 0, e: '' }
|
|
289
|
+
*/
|
|
290
|
+
export const removeNullish = (obj) => {
|
|
291
|
+
if (!obj || typeof obj !== 'object') return obj;
|
|
292
|
+
|
|
293
|
+
return Object.keys(obj).reduce((result, key) => {
|
|
294
|
+
if (obj[key] != null) {
|
|
295
|
+
result[key] = obj[key];
|
|
296
|
+
}
|
|
297
|
+
return result;
|
|
298
|
+
}, {});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Checks if two objects are deeply equal
|
|
303
|
+
* @param {Object} obj1 - First object
|
|
304
|
+
* @param {Object} obj2 - Second object
|
|
305
|
+
* @returns {boolean} True if equal
|
|
306
|
+
* @example
|
|
307
|
+
* isEqual({ a: 1 }, { a: 1 }); // true
|
|
308
|
+
*/
|
|
309
|
+
export const isEqual = (obj1, obj2) => {
|
|
310
|
+
if (obj1 === obj2) return true;
|
|
311
|
+
|
|
312
|
+
if (obj1 == null || obj2 == null) return false;
|
|
313
|
+
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
|
|
314
|
+
|
|
315
|
+
const keys1 = Object.keys(obj1);
|
|
316
|
+
const keys2 = Object.keys(obj2);
|
|
317
|
+
|
|
318
|
+
if (keys1.length !== keys2.length) return false;
|
|
319
|
+
|
|
320
|
+
for (const key of keys1) {
|
|
321
|
+
if (!keys2.includes(key)) return false;
|
|
322
|
+
if (!isEqual(obj1[key], obj2[key])) return false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Filters object by predicate function
|
|
330
|
+
* @param {Object} obj - The object
|
|
331
|
+
* @param {Function} fn - Predicate function
|
|
332
|
+
* @returns {Object} Filtered object
|
|
333
|
+
* @example
|
|
334
|
+
* filterObject({ a: 1, b: 2, c: 3, d: 4 }, val => val % 2 === 0); // { b: 2, d: 4 }
|
|
335
|
+
*/
|
|
336
|
+
export const filterObject = (obj, fn) => {
|
|
337
|
+
if (!obj || typeof obj !== 'object' || typeof fn !== 'function') return {};
|
|
338
|
+
|
|
339
|
+
return Object.keys(obj).reduce((result, key) => {
|
|
340
|
+
if (fn(obj[key], key, obj)) {
|
|
341
|
+
result[key] = obj[key];
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
}, {});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Converts object to query string
|
|
349
|
+
* @param {Object} obj - The object
|
|
350
|
+
* @returns {string} Query string
|
|
351
|
+
* @example
|
|
352
|
+
* toQueryString({ name: 'John', age: 30 }); // 'name=John&age=30'
|
|
353
|
+
*/
|
|
354
|
+
export const toQueryString = (obj) => {
|
|
355
|
+
if (!obj || typeof obj !== 'object') return '';
|
|
356
|
+
|
|
357
|
+
return Object.keys(obj)
|
|
358
|
+
.filter(key => obj[key] != null)
|
|
359
|
+
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
|
|
360
|
+
.join('&');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Converts query string to object
|
|
365
|
+
* @param {string} queryString - The query string
|
|
366
|
+
* @returns {Object} Parsed object
|
|
367
|
+
* @example
|
|
368
|
+
* fromQueryString('name=John&age=30'); // { name: 'John', age: 30 }
|
|
369
|
+
*/
|
|
370
|
+
export const fromQueryString = (queryString) => {
|
|
371
|
+
if (!queryString || typeof queryString !== 'string') return {};
|
|
372
|
+
|
|
373
|
+
const params = new URLSearchParams(queryString);
|
|
374
|
+
const result = {};
|
|
375
|
+
|
|
376
|
+
for (const [key, value] of params) {
|
|
377
|
+
result[key] = value;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Gets object size (number of properties)
|
|
385
|
+
* @param {Object} obj - The object
|
|
386
|
+
* @returns {number} Number of properties
|
|
387
|
+
* @example
|
|
388
|
+
* size({ a: 1, b: 2, c: 3 }); // 3
|
|
389
|
+
* size({}); // 0
|
|
390
|
+
*/
|
|
391
|
+
export const size = (obj) => {
|
|
392
|
+
if (!obj || typeof obj !== 'object') return 0;
|
|
393
|
+
return Object.keys(obj).length;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Checks if an object has a property.
|
|
398
|
+
* @param {Object} obj - The object to check.
|
|
399
|
+
* @param {string} prop - The property to check.
|
|
400
|
+
* @returns {boolean} True if the property exists.
|
|
401
|
+
* @example
|
|
402
|
+
* has({a: 1}, 'a'); // true
|
|
403
|
+
* has({ a: 1 }, 'b') // false
|
|
404
|
+
*/
|
|
405
|
+
export const has = (obj, prop) => {
|
|
406
|
+
if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
|
|
407
|
+
return prop in obj;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Checks if object has a nested property
|
|
412
|
+
* @param {Object} obj - The object
|
|
413
|
+
* @param {string} path - Path to check
|
|
414
|
+
* @returns {boolean} True if property exists
|
|
415
|
+
* @example
|
|
416
|
+
* hasPath({ a: { b: { c: 1 } } }, 'a.b.c'); // true
|
|
417
|
+
* hasPath({ a: { b: { c: 1 } } }, 'a.b.d'); // false
|
|
418
|
+
*/
|
|
419
|
+
export const hasPath = (obj, path) => {
|
|
420
|
+
if (!obj || typeof path !== 'string') return false;
|
|
421
|
+
const notFound = Symbol('not-found');
|
|
422
|
+
return getNestedValue(obj, path, notFound) !== notFound;
|
|
423
|
+
};
|