some-common-functions-js 1.1.0 → 1.1.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/CHANGELOG.md +9 -1
- package/README.md +182 -46
- package/index.js +150 -84
- package/package.json +3 -6
package/CHANGELOG.md
CHANGED
|
@@ -33,7 +33,15 @@ Improve README.md documentation.
|
|
|
33
33
|
## Version 1.0.9 - 2025/11/28 - ITA
|
|
34
34
|
- Provided more documentation in test.js to remind a developer how to symbolic-link (mimick as installed) the package and running the test code.
|
|
35
35
|
- Added new function, hasOnlyAll(), and updated the README.md documentation accordingly.
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
## Version 1.1.0 - 2025/12/22 - ITA
|
|
38
38
|
- Improved documentation.
|
|
39
39
|
- Moved in more functions to the package: `deepClone(anObject)`, `getSortedObject(anObject)`, `timeStampYyyyMmDd(dateInstance)` and `timeStampString(dateInstance)`.
|
|
40
|
+
|
|
41
|
+
## Version 1.1.1 - 2025/12/26 - ITA
|
|
42
|
+
- Removed lodash dependency and re-implemented the get() and set() object functions, reducing package size.
|
|
43
|
+
- Re-implemented the getSortedObj() function to no longer use the get() object function.
|
|
44
|
+
- Improved the duplicate removal process in getArrayWithNoDuplicates() function, so as to be more reliable across all cases. Implemented the new getNextDifferentObject() function in the light of that.
|
|
45
|
+
|
|
46
|
+
## Version 1.1.2 - 2025/12/26 - ITA
|
|
47
|
+
Corrected a minor error in the README documentation.
|
package/README.md
CHANGED
|
@@ -6,16 +6,18 @@ Common functions used for working with JavaScript objects and validating field v
|
|
|
6
6
|
## Installation
|
|
7
7
|
```
|
|
8
8
|
npm install some-common-functions-js
|
|
9
|
-
```
|
|
9
|
+
```
|
|
10
|
+
|
|
10
11
|
## 1. JavaScript Object Utilities
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
### `deepClone(anObject)`
|
|
13
|
-
Returns a deep clone of a plain Javascript object. The clone, while it has field equal to that of the original object, is separate from the original object.
|
|
14
|
-
|
|
14
|
+
Returns a deep clone of a plain Javascript object. The clone, while it has field equal to that of the original object, is separate from the original object.
|
|
15
|
+
|
|
15
16
|
### `getPaths(anObject)`
|
|
16
17
|
Returns a string array of path/field names inside a JavaScript object.
|
|
18
|
+
|
|
17
19
|
***Example***
|
|
18
|
-
```
|
|
20
|
+
```
|
|
19
21
|
const { getPaths } = require("some-common-functions-js");
|
|
20
22
|
let client = {
|
|
21
23
|
name: "Jack",
|
|
@@ -35,11 +37,14 @@ let client = {
|
|
|
35
37
|
let paths = getPaths(client);
|
|
36
38
|
// ["name", "surname", "address.streetNum", "address.streetName", "address.suburb",
|
|
37
39
|
// "address.town", "address.country.name", "address.country.code"]
|
|
38
|
-
```
|
|
40
|
+
```
|
|
41
|
+
|
|
39
42
|
### `getSortedObject(anObject)`
|
|
40
|
-
Returns an object with sorted fields,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
Returns an object with sorted fields, ordered by field name ascending.
|
|
44
|
+
|
|
45
|
+
***Examples***
|
|
46
|
+
```
|
|
47
|
+
const { get } = require("common-functions-js");
|
|
43
48
|
const client = {
|
|
44
49
|
firstName: "Isaiah",
|
|
45
50
|
lastName: "Tshabalala",
|
|
@@ -74,11 +79,90 @@ const sortedObject = getSortedObject(client);
|
|
|
74
79
|
lastName: 'Tshabalala'
|
|
75
80
|
}
|
|
76
81
|
*/
|
|
77
|
-
```
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `get(anObject, path)`
|
|
85
|
+
Returns the value of an object at the specified path.
|
|
86
|
+
Can take the place of lodash get() function.
|
|
87
|
+
|
|
88
|
+
***Examples***
|
|
89
|
+
```
|
|
90
|
+
const { get } = require("some-common-functions-js");
|
|
91
|
+
const client = {
|
|
92
|
+
firstName: "Isaiah",
|
|
93
|
+
lastName: "Tshabalala",
|
|
94
|
+
address: {
|
|
95
|
+
houseNum: "5520",
|
|
96
|
+
streetName: "Main Road",
|
|
97
|
+
mainPlace: "Evaton",
|
|
98
|
+
subPlace: "Evaton Small Farms",
|
|
99
|
+
city: "Vereeniging",
|
|
100
|
+
country: {
|
|
101
|
+
name: "South Africa",
|
|
102
|
+
code: "ZA"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
let result = get(client, "address.country");
|
|
108
|
+
// { name: "South Africa", code: "ZA" }
|
|
109
|
+
|
|
110
|
+
result = get(client, "address.country.code");
|
|
111
|
+
// "ZA"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `set(anObject, path)`
|
|
115
|
+
Sets the value of an object at the specified path.
|
|
116
|
+
Can take the place of lodash set() function.
|
|
117
|
+
|
|
118
|
+
***Examples***
|
|
119
|
+
```
|
|
120
|
+
const { set } = require("some-common-functions-js");
|
|
121
|
+
|
|
122
|
+
let emptyObj = {};
|
|
123
|
+
set(emptyObj, "address.country.name", "South Africa");
|
|
124
|
+
set(emptyObj, "address.country.code", "ZA");
|
|
125
|
+
set(emptyObj, "firstName", "Isaiah");
|
|
126
|
+
set(emptyObj, "lastName", "Tshabalala");
|
|
127
|
+
console.log(emptyObj);
|
|
128
|
+
/*
|
|
129
|
+
{
|
|
130
|
+
address: { country: { name: 'South Africa', code: 'ZA' } },
|
|
131
|
+
firstName: 'Isaiah',
|
|
132
|
+
lastName: 'Tshabalala'
|
|
133
|
+
}
|
|
134
|
+
*/
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `unset(anObject, path)`
|
|
138
|
+
Remove a field from an object at the specified path.
|
|
139
|
+
Can take the place of lodash unset() function.
|
|
140
|
+
|
|
141
|
+
***Example***
|
|
142
|
+
```
|
|
143
|
+
const {unset} = require("some-common-functions-js");
|
|
144
|
+
let testObj = {
|
|
145
|
+
firstName: 'John',
|
|
146
|
+
lastName: 'Rambo',
|
|
147
|
+
address: { country: { name: 'South Africa', code: 'ZA' } }
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
unset(testObj, "address.country.code");
|
|
151
|
+
unset(testObj, "lastName");
|
|
152
|
+
console.log(testObj); // Expecting to have "address.country.code" and "lastName" removed.
|
|
153
|
+
/*
|
|
154
|
+
{
|
|
155
|
+
firstName: 'John',
|
|
156
|
+
address: { country: { name: 'South Africa' } }
|
|
157
|
+
}
|
|
158
|
+
*/
|
|
159
|
+
```
|
|
160
|
+
|
|
78
161
|
### `hasOnly(anObject, ...fields)`
|
|
79
162
|
Returns `true` if the object contains **only** some or all of the specified fields and no others.
|
|
163
|
+
|
|
80
164
|
***Examples***
|
|
81
|
-
```
|
|
165
|
+
```
|
|
82
166
|
const { hasOnly } = require("some-common-functions-js");
|
|
83
167
|
|
|
84
168
|
let car = {
|
|
@@ -97,12 +181,14 @@ result = hasOnly(car, "maxSpeed", "gvm", "power");
|
|
|
97
181
|
|
|
98
182
|
result = hasOnly(car, "make", "model");
|
|
99
183
|
// false, because car has fields other than the specified fields.
|
|
100
|
-
```
|
|
184
|
+
```
|
|
185
|
+
|
|
101
186
|
### `hasAll(anObject, ...fields)`
|
|
102
187
|
Returns `true` if the object contains **all** the specified fields.
|
|
103
188
|
The object may contain additional fields.
|
|
189
|
+
|
|
104
190
|
***Examples***
|
|
105
|
-
```
|
|
191
|
+
```
|
|
106
192
|
const { hasAll } = require("some-common-functions-js");
|
|
107
193
|
let car = {
|
|
108
194
|
make: "Ford",
|
|
@@ -116,11 +202,13 @@ let result = hasAll(car, "make", "model");
|
|
|
116
202
|
|
|
117
203
|
let result = hasAll(car, "passengerCapacity", "year");
|
|
118
204
|
// false, because car does not have "passengerCapacity" field.
|
|
119
|
-
```
|
|
205
|
+
```
|
|
206
|
+
|
|
120
207
|
### `hasOnlyAll(anObject, ...fields)`
|
|
121
|
-
Return `true` if an object contains only all the specified fields, nothing more, nothing less
|
|
122
|
-
|
|
123
|
-
|
|
208
|
+
Return `true` if an object contains only all the specified fields, nothing more, nothing less
|
|
209
|
+
|
|
210
|
+
***Examples***
|
|
211
|
+
```
|
|
124
212
|
const { hasOnlyAll } = require("some-common-functions-js");
|
|
125
213
|
let car = {
|
|
126
214
|
make: "Ford",
|
|
@@ -327,20 +415,16 @@ console.log(objArray);
|
|
|
327
415
|
*/
|
|
328
416
|
let teams = [
|
|
329
417
|
{
|
|
330
|
-
score: 85,
|
|
331
|
-
numGames: 10
|
|
418
|
+
score: 85, numGames: 10
|
|
332
419
|
},
|
|
333
420
|
{
|
|
334
|
-
score: 90,
|
|
335
|
-
numGames: 12
|
|
421
|
+
score: 90, numGames: 12
|
|
336
422
|
},
|
|
337
423
|
{
|
|
338
|
-
score: 85,
|
|
339
|
-
numGames: 8
|
|
424
|
+
score: 85, numGames: 8
|
|
340
425
|
},
|
|
341
426
|
{
|
|
342
|
-
score: 90,
|
|
343
|
-
numGames: 10
|
|
427
|
+
score: 90, numGames: 10
|
|
344
428
|
}
|
|
345
429
|
];
|
|
346
430
|
// Using objCompare to sort fields where there are mixed sort directions.
|
|
@@ -396,35 +480,84 @@ Create an array of objects with duplicates eliminated. Taking only the first or
|
|
|
396
480
|
* The value of the comparison field must include both the field name and sort direction.
|
|
397
481
|
* Sort direction assumed to be "asc" if not provided.
|
|
398
482
|
* Examples of comparison fields: "firstName", "lastName desc", "address.province asc", "address.townOrCity".
|
|
399
|
-
|
|
483
|
+
|
|
400
484
|
***Example***
|
|
401
485
|
```
|
|
402
486
|
const { getObjArrayWithNoDuplicates } = require("some-common-functions-js");
|
|
403
|
-
let teamsArray = [
|
|
404
|
-
{ score: 90, numGames: 10 },
|
|
405
|
-
{ score: 90, numGames: 10 },
|
|
406
|
-
{ score: 90, numGames: 10 },
|
|
407
|
-
{ score: 90, numGames: 12 },
|
|
408
|
-
{ score: 90, numGames: 12 },
|
|
409
|
-
{ score: 90, numGames: 12 },
|
|
410
|
-
{ score: 85, numGames: 8 },
|
|
411
|
-
{ score: 85, numGames: 8 },
|
|
412
|
-
{ score: 85, numGames: 10 },
|
|
413
|
-
{ score: 85, numGames: 10 },
|
|
414
|
-
{ score: 85, numGames: 10 }
|
|
487
|
+
let teamsArray = [
|
|
488
|
+
{ score: 90, numGames: 10, name: "John" },
|
|
489
|
+
{ score: 90, numGames: 10, name: "Jane" },
|
|
490
|
+
{ score: 90, numGames: 10, name: "Bob" },
|
|
491
|
+
{ score: 90, numGames: 12, name: "Alice" },
|
|
492
|
+
{ score: 90, numGames: 12, name: "Charlie" },
|
|
493
|
+
{ score: 90, numGames: 12, name: "David" },
|
|
494
|
+
{ score: 85, numGames: 8, name: "Eve" },
|
|
495
|
+
{ score: 85, numGames: 8, name: "Frank" },
|
|
496
|
+
{ score: 85, numGames: 10, name: "Grace" },
|
|
497
|
+
{ score: 85, numGames: 10, name: "Henry" },
|
|
498
|
+
{ score: 85, numGames: 10, name: "Ivy" }
|
|
415
499
|
]; // Sorted by "score desc", "numGames asc".
|
|
416
500
|
|
|
417
501
|
let noDuplicatesArray = getObjArrayWithNoDuplicates(teamsArray, true, "score desc", "numGames asc");
|
|
418
|
-
console.log(noDuplicatesArray);
|
|
502
|
+
console.log(noDuplicatesArray);
|
|
503
|
+
// Should contain only unique objects according to comparison fields. First object per duplicate group.
|
|
419
504
|
/*
|
|
420
|
-
[
|
|
421
|
-
{ score: 90, numGames: 10 },
|
|
422
|
-
{ score: 90, numGames: 12 },
|
|
423
|
-
{ score: 85, numGames: 8 },
|
|
424
|
-
{ score: 85, numGames: 10 }
|
|
425
|
-
]
|
|
505
|
+
[
|
|
506
|
+
{ score: 90, numGames: 10, name: 'John' },
|
|
507
|
+
{ score: 90, numGames: 12, name: 'Alice' },
|
|
508
|
+
{ score: 85, numGames: 8, name: 'Eve' },
|
|
509
|
+
{ score: 85, numGames: 10, name: 'Grace' }
|
|
510
|
+
]
|
|
426
511
|
*/
|
|
427
|
-
|
|
512
|
+
|
|
513
|
+
let noDuplicatesArray = getObjArrayWithNoDuplicates(teamsArray, true, "score desc", "numGames asc");
|
|
514
|
+
console.log(noDuplicatesArray);
|
|
515
|
+
// Should contain unique objects according to comparison fields. Last object per duplicate group.
|
|
516
|
+
/*
|
|
517
|
+
[
|
|
518
|
+
{ score: 90, numGames: 10, name: 'Bob' },
|
|
519
|
+
{ score: 90, numGames: 12, name: 'David' },
|
|
520
|
+
{ score: 85, numGames: 8, name: 'Frank' },
|
|
521
|
+
{ score: 85, numGames: 10, name: 'Ivy' }
|
|
522
|
+
]
|
|
523
|
+
*/
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### `getNextDifferent(objArray, targetObj, startFrom, ...comparisonFields)`
|
|
527
|
+
Get the index in the array of objects, of the next element that is distinct from the target object.
|
|
528
|
+
Comparison fields must match the field & sort order of the object array.
|
|
529
|
+
Throw an error if the target object is greater than objArray[startFrom] in terms of sort order.
|
|
530
|
+
Examples of comparison fields: "firstName", "lastName desc", "address.province asc", "address.townOrCity".
|
|
531
|
+
|
|
532
|
+
***Example***
|
|
533
|
+
```
|
|
534
|
+
const { getNextDifferent } = require("some-common-functions-js");
|
|
535
|
+
|
|
536
|
+
teamsArray = [
|
|
537
|
+
{ score: 90, numGames: 10, name: "John" },
|
|
538
|
+
{ score: 90, numGames: 10, name: "Jane" },
|
|
539
|
+
{ score: 90, numGames: 10, name: "Bob" },
|
|
540
|
+
{ score: 90, numGames: 12, name: "Alice" },
|
|
541
|
+
{ score: 90, numGames: 12, name: "Charlie" },
|
|
542
|
+
{ score: 90, numGames: 12, name: "David" },
|
|
543
|
+
{ score: 85, numGames: 8, name: "Eve" },
|
|
544
|
+
{ score: 85, numGames: 8, name: "Frank" },
|
|
545
|
+
{ score: 85, numGames: 10, name: "Grace" },
|
|
546
|
+
{ score: 85, numGames: 10, name: "Henry" },
|
|
547
|
+
{ score: 85, numGames: 10, name: "Ivy" }
|
|
548
|
+
]; // Sorted by "score desc", "numGames asc".
|
|
549
|
+
|
|
550
|
+
let next = getNextDifferent(teamsArray, { score: 85, numGames: 8 }, 0, "score desc", "numGames asc");
|
|
551
|
+
// Throws an error because the startFrom index is to the left ('less than') the target object in terms of the field sort order.
|
|
552
|
+
|
|
553
|
+
next = getNextDifferent(teamsArray, { score: 90, numGames: 10 }, 0, "score desc", "numGames asc");
|
|
554
|
+
// 3
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
next = getNextDifferentObj(teamsArray, { score: 85, numGames: 10 }, 0, "score desc", "numGames asc");
|
|
558
|
+
// -1
|
|
559
|
+
```
|
|
560
|
+
|
|
428
561
|
## 4. Date Timestamp Functions
|
|
429
562
|
### `timeStampYyyyMmDd(dateInstance)`
|
|
430
563
|
Converts the date object to a string of the form CCYY-MM-DD
|
|
@@ -432,6 +565,9 @@ Converts the date object to a string of the form CCYY-MM-DD
|
|
|
432
565
|
### `timeStampString(dateInstance)`
|
|
433
566
|
Converts a date object to a string of the form CCYY-MM-DDThh:mm:ss.ccc, e.g. '2024-02-25T15:00:25.251'
|
|
434
567
|
|
|
568
|
+
### `addLeadingZeros(aNumber, newLength)`
|
|
569
|
+
Add leading zeros to a numerical string. E.g. addLeadingZeros(9, 3) = '009'
|
|
570
|
+
|
|
435
571
|
---
|
|
436
572
|
## License
|
|
437
573
|
MIT
|
package/index.js
CHANGED
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
* 2025/11/19 ITA 1.00 Genesis.
|
|
5
5
|
* 2025/11/28 ITA 1.01 Added function hasOnlyAll().
|
|
6
6
|
* 2025/12/22 ITA 1.02 Improved documentation of the functions and moved in more functions.
|
|
7
|
+
* 2025/12/30 ITA 1.03 Removed lodash dependency by re-implementing get() and set() object functions, significantly reducing this package size.
|
|
8
|
+
* Added function getNextDifferent() to deal better with duplicate removal from arrays of objects.
|
|
9
|
+
* 2026/01/02 ITA 1.04 Improved the functions getNoDuplicatesArray() and getNextDifferent() to handle more test cases.
|
|
10
|
+
* Added function unset().
|
|
7
11
|
*/
|
|
8
|
-
const loDash = require('lodash');
|
|
9
12
|
|
|
10
13
|
/**Return true if userName is valid
|
|
11
14
|
* @param {string} userName
|
|
@@ -203,27 +206,101 @@ function getPaths(anObject) {
|
|
|
203
206
|
} // function getPaths()
|
|
204
207
|
module.exports.getPaths = getPaths;
|
|
205
208
|
|
|
206
|
-
/** Return an object with sorted fields,
|
|
209
|
+
/** Return an object with sorted fields, ordered by field name ascending.
|
|
207
210
|
* This is desirable when equality comparison is done to ensure two objects sharing equal field values
|
|
208
211
|
* the pass the equality test stringify(object1) === stringify(object2)
|
|
209
212
|
* @param {object} pObject
|
|
210
213
|
* @returns {object} an object with fields sorted in ascending order of field names.
|
|
211
214
|
*/
|
|
212
215
|
function getSortedObject(pObject) {
|
|
216
|
+
const objClone = deepClone(pObject);
|
|
217
|
+
const paths = [];
|
|
218
|
+
const sortedObject = {};
|
|
213
219
|
|
|
214
|
-
|
|
220
|
+
// Obtain the outermost fields and sort them.
|
|
221
|
+
for (let field in objClone) {
|
|
222
|
+
paths.push(field);
|
|
223
|
+
}
|
|
215
224
|
paths.sort();
|
|
216
|
-
const sortedObject = {};
|
|
217
225
|
|
|
226
|
+
// Assign the sorted fields to the new object.
|
|
218
227
|
for (let index in paths) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
const field = paths[index];
|
|
229
|
+
if (Object.prototype.toString.call(objClone[field]) === '[object Object]') {
|
|
230
|
+
sortedObject[field] = getSortedObject(objClone[field]);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
sortedObject[field] = objClone[field];
|
|
234
|
+
} //
|
|
235
|
+
} // for (let field in paths) {
|
|
236
|
+
|
|
223
237
|
return sortedObject;
|
|
224
238
|
} // function getSortedObject(pObject) {
|
|
225
239
|
module.exports.getSortedObject = getSortedObject;
|
|
226
240
|
|
|
241
|
+
/** Get the value of a field specified by the path from an object.
|
|
242
|
+
* @param {object} anObject a Javascript object.
|
|
243
|
+
* @param {string} path a path specifying the field whose value is to be obtained.
|
|
244
|
+
* @returns {*} the value of the field specified by the path.
|
|
245
|
+
*/
|
|
246
|
+
function get(anObject, path) {
|
|
247
|
+
if (getPaths(anObject).includes(path) === false) {
|
|
248
|
+
console.log(hasAll(anObject, path), path, anObject, getPaths(anObject));
|
|
249
|
+
throw new Error(`Path ${path} does not exist on the object.`);
|
|
250
|
+
}
|
|
251
|
+
let paths = path.split('.');
|
|
252
|
+
let currentObj = deepClone(anObject);
|
|
253
|
+
|
|
254
|
+
let value = currentObj[paths[0]];
|
|
255
|
+
if (paths.length > 1) {
|
|
256
|
+
paths.splice(0, 1);
|
|
257
|
+
return get(value, paths.join('.'));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
module.exports.get = get;
|
|
264
|
+
|
|
265
|
+
/** Set the value of a field specified by the path on an object.
|
|
266
|
+
* @param {object} anObject a Javascript object.
|
|
267
|
+
* @param {string} path a path specifying the field whose value is to be set.
|
|
268
|
+
* @param {*} value the value to set.
|
|
269
|
+
*/
|
|
270
|
+
function set(anObject, path, value) {
|
|
271
|
+
let paths = path.split('.');
|
|
272
|
+
if (paths.length > 1) {
|
|
273
|
+
if (!anObject[paths[0]]) {
|
|
274
|
+
anObject[paths[0]] = {};
|
|
275
|
+
}
|
|
276
|
+
const subObject = anObject[paths[0]];
|
|
277
|
+
paths.splice(0, 1);
|
|
278
|
+
set(subObject, paths.join('.'), value);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
anObject[paths[0]] = value;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
module.exports.set = set;
|
|
285
|
+
|
|
286
|
+
/** Unset the value of a field specified by the path on an object.
|
|
287
|
+
* @param {object} anObject a Javascript object.
|
|
288
|
+
* @param {string} path a path specifying the field whose value is to be set.
|
|
289
|
+
* @param {*} value the value to set.
|
|
290
|
+
*/
|
|
291
|
+
function unset(anObject, path) {
|
|
292
|
+
let paths = path.split('.');
|
|
293
|
+
if (paths.length > 1) {
|
|
294
|
+
const subObject = anObject[paths[0]];
|
|
295
|
+
paths.splice(0, 1);
|
|
296
|
+
unset(subObject, paths.join('.'));
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
delete anObject[paths[0]];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
module.exports.unset = unset;
|
|
303
|
+
|
|
227
304
|
/**
|
|
228
305
|
* Determine whether an object contains only 1, some or all of the specified fields, and not any other fields.
|
|
229
306
|
* @param {object} anObject a Javascript object.
|
|
@@ -393,6 +470,42 @@ function binarySearchObj(objArray, searchObj, startFrom = 0, ...sortFields) {
|
|
|
393
470
|
} // function binarySearchObj(objArray, searchObj, ...comparisonFields) {
|
|
394
471
|
module.exports.binarySearchObj = binarySearchObj;
|
|
395
472
|
|
|
473
|
+
/**Get the index of the first element in an object array that is different from the target element
|
|
474
|
+
* according to the comparison fields.
|
|
475
|
+
* @param {Array<object>} objArray an array of objects
|
|
476
|
+
* @param {object} targetObj target object
|
|
477
|
+
* @param {number} startFrom index from which to start searching
|
|
478
|
+
* @param {...string} comparisonFields comparison fields plus sort order.
|
|
479
|
+
* @returns index of the next different object.
|
|
480
|
+
*/
|
|
481
|
+
function getNextDifferent(objArray, targetObj, startFrom, ...comparisonFields) {
|
|
482
|
+
let start = startFrom,
|
|
483
|
+
end = objArray.length - 1;
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
if (start >= objArray.length) { // throw error if startFrom is outside the bounds of the array.
|
|
487
|
+
throw new Error('startFrom is outside the bounds of the array.');
|
|
488
|
+
}
|
|
489
|
+
// If target object is to the right of objArray[start], then throw an error..
|
|
490
|
+
if (objCompare(targetObj, objArray[start], ...comparisonFields) > 0) {
|
|
491
|
+
throw new Error('targetObj is to the right (\'greater than\') objArray[startFrom].');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
while (start < end) {
|
|
495
|
+
let mid = Math.trunc((start + end) / 2);
|
|
496
|
+
if (objCompare(targetObj, objArray[mid], ...comparisonFields) === 0) {
|
|
497
|
+
start = mid + 1;
|
|
498
|
+
}
|
|
499
|
+
else if (objCompare(targetObj, objArray[mid], ...comparisonFields) < 0) {
|
|
500
|
+
end = mid;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (objCompare(targetObj, objArray[start], ...comparisonFields) === 0)
|
|
504
|
+
return -1;
|
|
505
|
+
return start;
|
|
506
|
+
}
|
|
507
|
+
module.exports.getNextDifferent = getNextDifferent;
|
|
508
|
+
|
|
396
509
|
/**Create an array with duplicates eliminated, according to certain fields. Taking only the first or last object from each duplicate set.
|
|
397
510
|
* If firstOfDuplicates === true, then the first element in each set of duplicates is taken.
|
|
398
511
|
* if firstOfDuplicates === false, then the last element is taken from each set of duplicates.
|
|
@@ -407,89 +520,42 @@ module.exports.binarySearchObj = binarySearchObj;
|
|
|
407
520
|
* @returns {Array<object>} an array with no duplicates.
|
|
408
521
|
*/
|
|
409
522
|
function getObjArrayWithNoDuplicates(objArray, firstOfDuplicates, ...comparisonFields) {
|
|
410
|
-
function getNextSearchObj(pNext) {
|
|
411
|
-
const nextObj = {...objArray[next]};
|
|
412
|
-
let lastField;
|
|
413
|
-
if (comparisonFields.length > 0)
|
|
414
|
-
lastField = comparisonFields[comparisonFields.length - 1].split(' ');
|
|
415
|
-
else
|
|
416
|
-
throw new Error('Supply atleast 1 comparisonFields parameter.');
|
|
417
|
-
|
|
418
|
-
const lastFieldName = lastField[0];
|
|
419
|
-
const sortDir = lastField.length > 1? lastField[1] : 'asc';
|
|
420
|
-
const lastFieldValue = loDash.get(nextObj, lastFieldName);
|
|
421
|
-
|
|
422
|
-
if (typeof lastFieldValue === 'number') {
|
|
423
|
-
if (sortDir === 'asc')
|
|
424
|
-
loDash.set(nextObj, lastFieldName, 1e-10 + lastFieldValue);
|
|
425
|
-
else
|
|
426
|
-
loDash.set(nextObj, lastFieldName, -1e-10 + lastFieldValue);
|
|
427
|
-
}
|
|
428
|
-
else if (typeof lastFieldValue === 'string') { // instance of String
|
|
429
|
-
if (sortDir === 'asc')
|
|
430
|
-
loDash.set(nextObj, lastFieldName, lastFieldValue + ' ');
|
|
431
|
-
else
|
|
432
|
-
loDash.set(nextObj, lastFieldName, ' ' + lastFieldValue);
|
|
433
|
-
}
|
|
434
|
-
else if (lastFieldValue instanceof Date) {
|
|
435
|
-
if (sortDir === 'asc')
|
|
436
|
-
loDash.set(nextObj, lastFieldName, new Date(1 + lastFieldValue.getTime()));
|
|
437
|
-
else
|
|
438
|
-
loDash.set(nextObj, lastFieldName, new Date(-1 + lastFieldValue.getTime()));
|
|
439
|
-
}
|
|
440
|
-
else
|
|
441
|
-
throw new Error(`${lastFieldName} must be type Number, String or Date`);
|
|
442
|
-
|
|
443
|
-
return nextObj;
|
|
444
|
-
} // function getNextSearchObj(pNext)
|
|
445
523
|
|
|
446
524
|
if (objArray.length <= 1)
|
|
447
525
|
return [...objArray];
|
|
448
526
|
|
|
449
|
-
if (
|
|
450
|
-
throw new Error(`firstOfDuplicates must be
|
|
527
|
+
if (typeof firstOfDuplicates !== 'boolean')
|
|
528
|
+
throw new Error(`firstOfDuplicates must be boolean true or false.`);
|
|
451
529
|
|
|
452
530
|
const noDuplicates = [];
|
|
531
|
+
let idx = 0;
|
|
532
|
+
let grpStart = 0; // Start index of current duplicate group.
|
|
533
|
+
while (grpStart < objArray.length - 1) {
|
|
534
|
+
if (firstOfDuplicates) {
|
|
535
|
+
noDuplicates.push(objArray[grpStart]);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
grpStart = getNextDifferent(objArray, objArray[grpStart], grpStart + 1, ...comparisonFields);
|
|
539
|
+
if (grpStart < 0)
|
|
540
|
+
break; // No more different objects.
|
|
453
541
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
noDuplicates.push(objArray[next]);
|
|
458
|
-
}
|
|
459
|
-
nextSearchObj = getNextSearchObj(objArray[next]);
|
|
460
|
-
|
|
461
|
-
while (next < objArray.length) {
|
|
462
|
-
// The aim is to jump to the next element that is not a duplicate of objArray[next].
|
|
463
|
-
next = binarySearchObj(objArray, nextSearchObj, next, ...comparisonFields);
|
|
464
|
-
let comparison = objCompare(objArray[next], nextSearchObj, ...comparisonFields);
|
|
465
|
-
if (comparison < 0) {
|
|
466
|
-
if (firstOfDuplicates) {
|
|
467
|
-
next++;
|
|
468
|
-
if (next < objArray.length) {
|
|
469
|
-
noDuplicates.push(objArray[next]);
|
|
470
|
-
nextSearchObj = getNextSearchObj(objArray[next]);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
else {
|
|
474
|
-
noDuplicates.push(objArray[next]);
|
|
475
|
-
next++;
|
|
476
|
-
if (next < objArray.length)
|
|
477
|
-
nextSearchObj = getNextSearchObj(objArray[next]);
|
|
478
|
-
}
|
|
479
|
-
continue;
|
|
542
|
+
let grpEnd = grpStart - 1;
|
|
543
|
+
if (!firstOfDuplicates) {
|
|
544
|
+
noDuplicates.push(objArray[grpEnd]);
|
|
480
545
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
546
|
+
idx = grpStart;
|
|
547
|
+
}
|
|
548
|
+
if (noDuplicates.length === 0) { // All objects are duplicates.
|
|
549
|
+
if (firstOfDuplicates)
|
|
550
|
+
noDuplicates.push(objArray[0]);
|
|
551
|
+
else
|
|
552
|
+
noDuplicates.push(objArray[objArray.length - 1]);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
if (objCompare(noDuplicates[noDuplicates.length - 1], objArray[objArray.length - 1], ...comparisonFields) !== 0) {
|
|
556
|
+
noDuplicates.push(objArray[objArray.length - 1]);
|
|
488
557
|
}
|
|
489
|
-
|
|
490
|
-
nextSearchObj = getNextSearchObj(objArray[next]);
|
|
491
|
-
next++;
|
|
492
|
-
} // while (comparison !== 0 && next < objArray.length) {
|
|
558
|
+
}
|
|
493
559
|
|
|
494
560
|
return noDuplicates;
|
|
495
561
|
} // function getObjArrayWithNoDuplicates(objArray, ...comparisonFields) {
|
|
@@ -519,8 +585,8 @@ function objCompare(obj1, obj2, ...comparisonFields) {
|
|
|
519
585
|
if (!sortDirections.includes(sortDir))
|
|
520
586
|
throw new Error('Sort direction must be one of ' + sortDirections.toString());
|
|
521
587
|
|
|
522
|
-
const value1 =
|
|
523
|
-
const value2 =
|
|
588
|
+
const value1 = get(obj1, fieldName);
|
|
589
|
+
const value2 = get(obj2, fieldName);
|
|
524
590
|
|
|
525
591
|
const returnValue = (sortDir === 'desc'? -1: 1);
|
|
526
592
|
if (value1 > value2)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "some-common-functions-js",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Common functions used with Javascript objects, and field validation functions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"validation",
|
|
@@ -23,21 +23,18 @@
|
|
|
23
23
|
"object utilities",
|
|
24
24
|
"object validation",
|
|
25
25
|
"object array search",
|
|
26
|
-
"object
|
|
26
|
+
"object compare",
|
|
27
27
|
"nodejs"
|
|
28
28
|
],
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
31
|
"url": "https://github.com/IsaiahTshabalala/common-functions"
|
|
32
32
|
},
|
|
33
|
-
"license": "
|
|
33
|
+
"license": "MIT",
|
|
34
34
|
"author": "ITA",
|
|
35
35
|
"type": "commonjs",
|
|
36
36
|
"main": "index.js",
|
|
37
37
|
"scripts": {
|
|
38
38
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
39
|
-
},
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"lodash": "^4.17.21"
|
|
42
39
|
}
|
|
43
40
|
}
|