some-common-functions-js 1.0.9 → 1.1.2

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +173 -26
  3. package/index.js +263 -90
  4. package/package.json +1 -4
package/CHANGELOG.md CHANGED
@@ -28,8 +28,20 @@ Improve README.md documentation.
28
28
  - Corrected README documentation.
29
29
 
30
30
  ## Version 1.0.8 - 2025/11/21 - ITA
31
- - Corrected test code and README documentation
31
+ - Corrected test code and README documentation.
32
32
 
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
+
37
+ ## Version 1.1.0 - 2025/12/22 - ITA
38
+ - Improved documentation.
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
@@ -1,17 +1,23 @@
1
- # JavaScript Object Utilities & Field Validation Functions
1
+ # JavaScript Object Utilities, Field Validation Functions, Array Binary Search, and Date timestamp Functions.
2
2
 
3
3
  Common functions used for working with JavaScript objects and validating field values.
4
4
 
5
5
  ---
6
6
  ## Installation
7
- **npm install some-common-functions-js**
8
-
7
+ ```
8
+ npm install some-common-functions-js
9
+ ```
10
+
9
11
  ## 1. JavaScript Object Utilities
10
-
12
+
13
+ ### `deepClone(anObject)`
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
+
11
16
  ### `getPaths(anObject)`
12
17
  Returns a string array of path/field names inside a JavaScript object.
18
+
13
19
  ***Example***
14
- ```
20
+ ```
15
21
  const { getPaths } = require("some-common-functions-js");
16
22
  let client = {
17
23
  name: "Jack",
@@ -30,12 +36,109 @@ let client = {
30
36
 
31
37
  let paths = getPaths(client);
32
38
  // ["name", "surname", "address.streetNum", "address.streetName", "address.suburb",
33
- // "address.town", "address.country.name", "address.country.code"]
34
- ```
39
+ // "address.town", "address.country.name", "address.country.code"]
40
+ ```
41
+
42
+ ### `getSortedObject(anObject)`
43
+ Returns an object with sorted fields, ordered by field name ascending.
44
+
45
+ ***Examples***
46
+ ```
47
+ const { get } = require("common-functions-js");
48
+ const client = {
49
+ firstName: "Isaiah",
50
+ lastName: "Tshabalala",
51
+ address: {
52
+ houseNum: "5520",
53
+ streetName: "Main Road",
54
+ mainPlace: "Evaton",
55
+ subPlace: "Evaton Small Farms",
56
+ city: "Vereeniging",
57
+ country: {
58
+ name: "South Africa",
59
+ code: "ZA"
60
+ }
61
+ }
62
+ };
63
+
64
+ const sortedObject = getSortedObject(client);
65
+ /*
66
+ {
67
+ address: {
68
+ city: 'Vereeniging',
69
+ country: {
70
+ code: 'ZA',
71
+ name: 'South Africa'
72
+ },
73
+ houseNum: '5520',
74
+ mainPlace: 'Evaton',
75
+ streetName: 'Main Road',
76
+ subPlace: 'Evaton Small Farms'
77
+ },
78
+ firstName: 'Isaiah',
79
+ lastName: 'Tshabalala'
80
+ }
81
+ */
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
+
35
137
  ### `hasOnly(anObject, ...fields)`
36
138
  Returns `true` if the object contains **only** some or all of the specified fields and no others.
139
+
37
140
  ***Examples***
38
- ```
141
+ ```
39
142
  const { hasOnly } = require("some-common-functions-js");
40
143
 
41
144
  let car = {
@@ -54,12 +157,14 @@ result = hasOnly(car, "maxSpeed", "gvm", "power");
54
157
 
55
158
  result = hasOnly(car, "make", "model");
56
159
  // false, because car has fields other than the specified fields.
57
- ```
160
+ ```
161
+
58
162
  ### `hasAll(anObject, ...fields)`
59
163
  Returns `true` if the object contains **all** the specified fields.
60
164
  The object may contain additional fields.
165
+
61
166
  ***Examples***
62
- ```
167
+ ```
63
168
  const { hasAll } = require("some-common-functions-js");
64
169
  let car = {
65
170
  make: "Ford",
@@ -73,11 +178,13 @@ let result = hasAll(car, "make", "model");
73
178
 
74
179
  let result = hasAll(car, "passengerCapacity", "year");
75
180
  // false, because car does not have "passengerCapacity" field.
76
- ```
181
+ ```
182
+
77
183
  ### `hasOnlyAll(anObject, ...fields)`
78
- Return `true` if an object contains only all the specified fields, nothing more, nothing less
79
- ***Example***
80
- ```
184
+ Return `true` if an object contains only all the specified fields, nothing more, nothing less
185
+
186
+ ***Examples***
187
+ ```
81
188
  const { hasOnlyAll } = require("some-common-functions-js");
82
189
  let car = {
83
190
  make: "Ford",
@@ -284,20 +391,16 @@ console.log(objArray);
284
391
  */
285
392
  let teams = [
286
393
  {
287
- score: 85,
288
- numGames: 10
394
+ score: 85, numGames: 10
289
395
  },
290
396
  {
291
- score: 90,
292
- numGames: 12
397
+ score: 90, numGames: 12
293
398
  },
294
399
  {
295
- score: 85,
296
- numGames: 8
400
+ score: 85, numGames: 8
297
401
  },
298
402
  {
299
- score: 90,
300
- numGames: 10
403
+ score: 90, numGames: 10
301
404
  }
302
405
  ];
303
406
  // Using objCompare to sort fields where there are mixed sort directions.
@@ -344,7 +447,7 @@ let anIndex = binarySearchObj(teamsArray, searchObj, 0, "score desc", "numGames
344
447
 
345
448
  let result = objCompare(searchObj, teamsArray[anIndex], "score desc", "numGames asc"); // 0 -- an object with value { score: 85, numGames: 8} exists at teamsArray[anIndex];
346
449
  ```
347
- ## 4. `getObjArrayWithNoDuplicates(objArray, firstOfDuplicates, ...comparisonFields)`
450
+ ### `getObjArrayWithNoDuplicates(objArray, firstOfDuplicates, ...comparisonFields)`
348
451
  Create an array of objects with duplicates eliminated. Taking only the first or last object from each duplicate set. The input array must be sorted according to the values of comparisonFields.
349
452
  * If firstOfDuplicates === true, then the first element in each set of duplicates is taken.
350
453
  * if firstOfDuplicates === false, then the last element is taken from each set of duplicates.
@@ -353,7 +456,7 @@ Create an array of objects with duplicates eliminated. Taking only the first or
353
456
  * The value of the comparison field must include both the field name and sort direction.
354
457
  * Sort direction assumed to be "asc" if not provided.
355
458
  * Examples of comparison fields: "firstName", "lastName desc", "address.province asc", "address.townOrCity".
356
-
459
+
357
460
  ***Example***
358
461
  ```
359
462
  const { getObjArrayWithNoDuplicates } = require("some-common-functions-js");
@@ -381,8 +484,52 @@ console.log(noDuplicatesArray); // Should contain only unique objects according
381
484
  { score: 85, numGames: 10 }
382
485
  ]
383
486
  */
384
- ```
487
+ ```
488
+
489
+ ### `getNextDifferent(objArray, targetObj, startFrom, ...comparisonFields)`
490
+ Get the index in the array of objects, of the next element that is distinct from the target object.
491
+ Comparison fields must match the field & sort order of the object array.
492
+ Examples of comparison fields: "firstName", "lastName desc", "address.province asc", "address.townOrCity".
493
+
494
+ ***Example***
495
+ ```
496
+ const { getNextDifferentObj } = require("some-common-functions-js");
497
+
498
+ teamsArray = [
499
+ { score: 90, numGames: 10 },
500
+ { score: 90, numGames: 10 },
501
+ { score: 90, numGames: 10 },
502
+ { score: 90, numGames: 12 },
503
+ { score: 90, numGames: 12 },
504
+ { score: 90, numGames: 12 },
505
+ { score: 85, numGames: 8 },
506
+ { score: 85, numGames: 8 },
507
+ { score: 85, numGames: 10 },
508
+ { score: 85, numGames: 10 },
509
+ { score: 85, numGames: 10 }
510
+ ]; // Sorted by "score desc", "numGames asc".
511
+
512
+ let next = getNextDifferentObj(teamsArray, { score: 85, numGames: 8 }, 0, "score desc", "numGames asc");
513
+ // Throws an error because the startFrom index is to the left ('less than') the target object in terms of the field sort order.
514
+
515
+ next = getNextDifferentObj(teamsArray, { score: 90, numGames: 10 }, 0, "score desc", "numGames asc");
516
+ // 3
517
+
518
+
519
+ next = getNextDifferentObj(teamsArray, { score: 85, numGames: 10 }, 0, "score desc", "numGames asc");
520
+ // -1
521
+ ```
522
+
523
+ ## 4. Date Timestamp Functions
524
+ ### `timeStampYyyyMmDd(dateInstance)`
525
+ Converts the date object to a string of the form CCYY-MM-DD
526
+
527
+ ### `timeStampString(dateInstance)`
528
+ Converts a date object to a string of the form CCYY-MM-DDThh:mm:ss.ccc, e.g. '2024-02-25T15:00:25.251'
529
+
530
+ ### `addLeadingZeros(aNumber, newLength)`
531
+ Add leading zeros to a numerical string. E.g. addLeadingZeros(9, 3) = '009'
532
+
385
533
  ---
386
534
  ## License
387
535
  MIT
388
-
package/index.js CHANGED
@@ -3,9 +3,15 @@
3
3
  * Date Dev Version Description
4
4
  * 2025/11/19 ITA 1.00 Genesis.
5
5
  * 2025/11/28 ITA 1.01 Added function hasOnlyAll().
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. * .
6
9
  */
7
- const loDash = require('lodash');
8
10
 
11
+ /**Return true if userName is valid
12
+ * @param {string} userName
13
+ * @returns {boolean} true if a userName is valid, otherwise false.
14
+ */
9
15
  function isValidUserName(userName) {
10
16
  if (!userName) // The username must be provided.
11
17
  return false;
@@ -15,6 +21,10 @@ function isValidUserName(userName) {
15
21
  }
16
22
  module.exports.isValidUserName = isValidUserName;
17
23
 
24
+ /**Return true if a name is valid
25
+ * @param {string} name
26
+ * @returns {boolean} true if a name is valid, otherwise false.
27
+ */
18
28
  function isValidName(name) {
19
29
  if (!name) // The name must be provided.
20
30
  return false;
@@ -24,6 +34,10 @@ function isValidName(name) {
24
34
  }
25
35
  module.exports.isValidName = isValidName;
26
36
 
37
+ /**Return true if userName is valid
38
+ * @param {string} email
39
+ * @returns {boolean} true if an email is valid, otherwise false.
40
+ */
27
41
  function isValidEmail(email) {
28
42
  if (!email)
29
43
  return false;
@@ -33,6 +47,10 @@ function isValidEmail(email) {
33
47
  }
34
48
  module.exports.isValidEmail = isValidEmail;
35
49
 
50
+ /**Return true if userName is valid
51
+ * @param {string} num phone number
52
+ * @returns {boolean} true if phone number is valid, otherwise false.
53
+ */
36
54
  function isValidPhoneNum(num) {
37
55
  if (!num) // The number must be provided.
38
56
  return false;
@@ -42,6 +60,10 @@ function isValidPhoneNum(num) {
42
60
  }
43
61
  module.exports.isValidPhoneNum = isValidPhoneNum;
44
62
 
63
+ /**Return true if the name of an organisation is valid
64
+ * @param {string} name an organisation name
65
+ * @returns {boolean} true if an organisation name is valid, otherwise false.
66
+ */
45
67
  function isValidOrganisationName(name) {
46
68
  if (!name) // The name must be provided.
47
69
  return false;
@@ -51,6 +73,10 @@ function isValidOrganisationName(name) {
51
73
  }
52
74
  module.exports.isValidOrganisationName = isValidOrganisationName;
53
75
 
76
+ /**Return true if a password is valid
77
+ * @param {string} password
78
+ * @returns {boolean} true if a password is valid, otherwise false.
79
+ */
54
80
  function isValidPassword(password) {
55
81
  if (!password)
56
82
  return false;
@@ -83,9 +109,79 @@ function isValidPassword(password) {
83
109
  }
84
110
  module.exports.isValidPassword = isValidPassword;
85
111
 
112
+ /** Converts the date object to a string of the form CCYY-MM-DD
113
+ * @param {Date} dateObj
114
+ * @returns {string} string of the form CCYY-MM-DD
115
+ */
116
+ function timeStampYyyyMmDd(dateObj) {
117
+ // Convert the date to string form yyyy-mm-dd
118
+ let year = dateObj.getFullYear();
119
+ let month = dateObj.getMonth() + 1;
120
+ month = addLeadingZeros(month, 2);
121
+ let day = dateObj.getDate();
122
+ day = addLeadingZeros(day, 2);
123
+ return `${year}-${month}-${day}`;
124
+ } // function timeStampYYYYMMDd(dateObj) {
125
+ module.exports.timeStampYyyyMmDd = timeStampYyyyMmDd;
126
+
127
+ /** Converts a date object to a string of the form CCYY-MM-DDThh:mm:ss.ccc, e.g. '2024-02-25T15:00:25.251'
128
+ * @param {Date} dateObj
129
+ * @returns {string} a string of the form CCYY-MM-DDThh:mm:ss.ccc.
130
+ */
131
+ function timeStampString(dateObj) {
132
+ let hours = addLeadingZeros(dateObj.getHours(), 2);
133
+ let minutes = addLeadingZeros(dateObj.getMinutes(), 2);
134
+ let seconds = addLeadingZeros(dateObj.getSeconds(), 2);
135
+ let milliSec = addLeadingZeros(dateObj.getMilliseconds(), 3);
136
+ return `${timeStampYyyyMmDd(dateObj)}T${hours}:${minutes}:${seconds}.${milliSec}`;
137
+ } // function timeStampString(dateObj) {
138
+ module.exports.timeStampString = timeStampString;
139
+
140
+
141
+ /** Return a numeric string with trailing zeros.
142
+ * E.g. addLeadingZeros(9, 3) = '009'
143
+ * Inputs:
144
+ * @param {Number} aNumber an integer or integer string.
145
+ * @param {Number} newLength the new length of the resulting string.
146
+ * @returns a string of a number with the specified number of leading zeros.
147
+ */
148
+ function addLeadingZeros(aNumber, newLength) {
149
+
150
+ let newString = aNumber + '';
151
+ const howManyZeros = newLength - newString.length;
152
+ for (let count = 1; count <= howManyZeros; count++)
153
+ newString = '0' + newString;
154
+
155
+ return newString;
156
+ } // function addLeadingZeros(aString, newLength) {
157
+ module.exports.addLeadingZeros = addLeadingZeros;
158
+
159
+ /**Convert numeric input to ZAR currency format string.
160
+ * @param {Number} a number
161
+ * @returns a string of the form R 256,534.00
162
+ */
163
+ function toZarCurrencyFormat(number) {
164
+ const zarCurrencyFormat = new Intl.NumberFormat('en-US', {style: 'currency', currency: 'ZAR'});
165
+ return zarCurrencyFormat.format(number).replace(/ZAR/gi, 'R');
166
+ }
167
+ module.exports.toZarCurrencyFormat = toZarCurrencyFormat;
168
+
169
+ /**Return a deep clone of a document object.
170
+ * By using deep cloning, you create a new object that is entirely separate from the original original.
171
+ * So that whatever you do to that clone, such as deletion of fields, does not affect the original.
172
+ * NB. Class instance types will be converted to plain object types due to stringification.
173
+ * @param {object} obj a plain Javascript object.
174
+ * @returns a Javascript object that is separate from the original object.
175
+ */
176
+ function deepClone(obj) {
177
+ return JSON.parse(JSON.stringify(obj));
178
+ } // function deepClone(obj) { // Return a deep clone of an object.
179
+ module.exports.deepClone = deepClone;
180
+
181
+
86
182
  /**
87
183
  * Get the paths (fields) of the plain Javascript object.
88
- * @param {object} anObject
184
+ * @param {object} anObject a plain Javascript object.
89
185
  * @returns a sorted string array of paths.
90
186
  */
91
187
  function getPaths(anObject) {
@@ -108,11 +204,92 @@ function getPaths(anObject) {
108
204
  } // function getPaths()
109
205
  module.exports.getPaths = getPaths;
110
206
 
207
+ /** Return an object with sorted fields, ordered by field name ascending.
208
+ * This is desirable when equality comparison is done to ensure two objects sharing equal field values
209
+ * the pass the equality test stringify(object1) === stringify(object2)
210
+ * @param {object} pObject
211
+ * @returns {object} an object with fields sorted in ascending order of field names.
212
+ */
213
+ function getSortedObject(pObject) {
214
+ const objClone = deepClone(pObject);
215
+ const paths = [];
216
+ const sortedObject = {};
217
+
218
+ // Obtain the outermost fields and sort them.
219
+ for (let field in objClone) {
220
+ paths.push(field);
221
+ }
222
+ paths.sort();
223
+
224
+ // Assign the sorted fields to the new object.
225
+ for (let index in paths) {
226
+ const field = paths[index];
227
+ if (Object.prototype.toString.call(objClone[field]) === '[object Object]') {
228
+ sortedObject[field] = getSortedObject(objClone[field]);
229
+ }
230
+ else {
231
+ sortedObject[field] = objClone[field];
232
+ } //
233
+ } // for (let field in paths) {
234
+
235
+ return sortedObject;
236
+ } // function getSortedObject(pObject) {
237
+ module.exports.getSortedObject = getSortedObject;
238
+
239
+ /** Get the value of a field specified by the path from an object.
240
+ * @param {object} anObject a Javascript object.
241
+ * @param {string} path a path specifying the field whose value is to be obtained.
242
+ * @returns {*} the value of the field specified by the path.
243
+ */
244
+ function get(anObject, path) {
245
+ if (getPaths(anObject).includes(path) === false) {
246
+ console.log(hasAll(anObject, path), path, anObject, getPaths(anObject));
247
+ throw new Error(`Path ${path} does not exist on the object.`);
248
+ }
249
+ let paths = path.split('.');
250
+ let currentObj = deepClone(anObject);
251
+
252
+ let value = currentObj[paths[0]];
253
+ if (paths.length > 1) {
254
+ paths.splice(0, 1);
255
+ return get(value, paths.join('.'));
256
+ }
257
+ else {
258
+ return value;
259
+ }
260
+ }
261
+ module.exports.get = get;
262
+
263
+ /** Set the value of a field specified by the path on an object.
264
+ * @param {object} anObject a Javascript object.
265
+ * @param {string} path a path specifying the field whose value is to be set.
266
+ * @param {*} value the value to set.
267
+ */
268
+ function set(anObject, path, value) {
269
+ /*if (hasAll(anObject, path) === false) {
270
+ throw new Error(`Path ${path} does not exist on the object.`);
271
+ }*/
272
+
273
+ let paths = path.split('.');
274
+ if (paths.length > 1) {
275
+ if (!anObject[paths[0]]) {
276
+ anObject[paths[0]] = {};
277
+ }
278
+ const subObject = anObject[paths[0]];
279
+ paths.splice(0, 1);
280
+ set(subObject, paths.join('.'), value);
281
+ }
282
+ else {
283
+ anObject[paths[0]] = value;
284
+ }
285
+ }
286
+ module.exports.set = set;
287
+
111
288
  /**
112
- * Determine whether an object contains only some or all of the specified fields, and not any other fields.
113
- * @param {*} anObject a Javascript object.
289
+ * Determine whether an object contains only 1, some or all of the specified fields, and not any other fields.
290
+ * @param {object} anObject a Javascript object.
114
291
  * @param {...string} fields one or more field names.
115
- * @returns boolean.
292
+ * @returns boolean true or false.
116
293
  */
117
294
  function hasOnly(anObject, ...fields) {
118
295
  if (!fields || !fields.length)
@@ -134,10 +311,10 @@ function hasOnly(anObject, ...fields) {
134
311
  module.exports.hasOnly = hasOnly;
135
312
 
136
313
  /**
137
- * Determine whether an object contains all of the specified fields. It may have additional fields.
138
- * @param {*} anObject a Javascript object.
314
+ * Determine whether an object contains all of the specified fields in addition to other fields.
315
+ * @param {object} anObject a Javascript object.
139
316
  * @param {...string} fields one or field names.
140
- * @returns boolean.
317
+ * @returns boolean true or false.
141
318
  */
142
319
  function hasAll(anObject, ...fields) {
143
320
  if (!fields || !fields.length)
@@ -160,9 +337,9 @@ module.exports.hasAll = hasAll;
160
337
 
161
338
  /**
162
339
  * Determine whether an object contains only all of the specified fields. Nothing more, nothing less.
163
- * @param {*} anObject a Javascript object.
340
+ * @param {object} anObject a Javascript object.
164
341
  * @param {...string} fields one or field names.
165
- * @returns boolean.
342
+ * @returns boolean true or false.
166
343
  */
167
344
  function hasOnlyAll(anObject, ...fields) {
168
345
  return hasOnly(anObject, ...fields) && hasAll(anObject, ...fields);
@@ -175,6 +352,11 @@ module.exports.hasOnlyAll = hasOnlyAll;
175
352
  * otherwise, the index is of closest value in the array that is before or after the search value in terms of sort order.
176
353
  * Return -1 for an empty array.
177
354
  * This function is to be used also in cases where values are to be inserted into the array while maintaining sort order.
355
+ * @param {Array} anArray an array of primitve type. All element must be the same type.
356
+ * @param {*} searchVal search value
357
+ * @param {number} [startFrom=0] index from which to start. Default: 0.
358
+ * @param {string} [arraySortDir='asc'] sort direction. Must be 'asc' or 'desc'. Default: 'asc'
359
+ * @returns {number} an index
178
360
  */
179
361
  function binarySearch(anArray, searchVal, startFrom = 0, arraySortDir = 'asc') {
180
362
 
@@ -212,7 +394,10 @@ module.exports.binarySearch = binarySearch;
212
394
  * A return value of -1 means that value1 is before value2 in terms of sort order.
213
395
  * A return value of 1 means that value1 is after value2 in terms of sort order.
214
396
  * A return value of 0 means that value1 is equal to value2.
215
- * Sort directions: 'asc', 'desc'. Default is 'asc'.
397
+ * @param {*} value1
398
+ * @param {*} value2
399
+ * @param {string} [sortDir='asc']
400
+ * @returns {number} integer (-1, 0 or 1)
216
401
  */
217
402
  function compare(value1, value2, sortDir = 'asc') {
218
403
  if (!['asc', 'desc'].includes(sortDir))
@@ -236,8 +421,13 @@ module.exports.compare = compare;
236
421
  * Return -1 for an empty array.
237
422
  * Assumed field data types are Number, String and Date.
238
423
  * This function is to be used also in cases where objects are to be inserted into the array while maintaining sort order.
424
+ * @param {Array<object} objArray an array of Javascript objects.
425
+ * @param {object} searchObj an object to search for.
426
+ * @@param {number} [startFrom=0] index from which to start searching.
427
+ * @param {...string} sortFields one or more search fields.
428
+ * @returns {number} an index.
239
429
  */
240
- function binarySearchObj(objArray, searchObj, startFrom, ...sortFields) {
430
+ function binarySearchObj(objArray, searchObj, startFrom = 0, ...sortFields) {
241
431
  if (objArray.length === 0)
242
432
  return -1;
243
433
 
@@ -264,7 +454,38 @@ function binarySearchObj(objArray, searchObj, startFrom, ...sortFields) {
264
454
  } // function binarySearchObj(objArray, searchObj, ...comparisonFields) {
265
455
  module.exports.binarySearchObj = binarySearchObj;
266
456
 
267
- /**Create an array with duplicates eliminated. Taking only the first or last object from each duplicate set.
457
+ /**Get the index of the first element in an object array that is different from the target element
458
+ * according to the comparison fields.
459
+ * @param {Array<object>} objArray an array of objects
460
+ * @param {object} targetObj target object
461
+ * @param {number} startFrom index from which to start searching
462
+ * @param {...string} comparisonFields comparison fields plus sort order.
463
+ * @returns index of the next different object.
464
+ */
465
+ function getNextDifferent(objArray, targetObj, startFrom, ...comparisonFields) {
466
+ let start = startFrom,
467
+ end = objArray.length - 1;
468
+
469
+ // If target object is to the right of objArray[start], then throw an error..
470
+ if (objCompare(targetObj, objArray[start], ...comparisonFields) > 0)
471
+ throw new Error('targetObj is to the right (\'greater than\') objArray[startFrom].');
472
+
473
+ while (start < end) {
474
+ let mid = Math.trunc((start + end) / 2);
475
+ if (objCompare(targetObj, objArray[mid], ...comparisonFields) === 0) {
476
+ start = mid + 1;
477
+ }
478
+ else if (objCompare(targetObj, objArray[mid], ...comparisonFields) < 0) {
479
+ end = mid;
480
+ }
481
+ }
482
+ if (objCompare(targetObj, objArray[start], ...comparisonFields) === 0)
483
+ return -1;
484
+ return start;
485
+ }
486
+ module.exports.getNextDifferent = getNextDifferent;
487
+
488
+ /**Create an array with duplicates eliminated, according to certain fields. Taking only the first or last object from each duplicate set.
268
489
  * If firstOfDuplicates === true, then the first element in each set of duplicates is taken.
269
490
  * if firstOfDuplicates === false, then the last element is taken from each set of duplicates.
270
491
  * Assumed field data types are Number, String and Date.
@@ -272,91 +493,40 @@ module.exports.binarySearchObj = binarySearchObj;
272
493
  * The value of the comparison field must include both the field name and sort direction.
273
494
  * Sort direction assumed to be "asc" if not provided.
274
495
  * Examples of comparison fields: "firstName", "lastName desc", "address.province asc", "address.townOrCity".
496
+ * @param {Array<object>} objArray an input array of objects
497
+ * @param {boolean} firstOfDuplicates specify whether to take the first or last object in each a duplicate set.
498
+ * @param {...string} comparisonFields comparison fieds plus sort order.
499
+ * @returns {Array<object>} an array with no duplicates.
275
500
  */
276
501
  function getObjArrayWithNoDuplicates(objArray, firstOfDuplicates, ...comparisonFields) {
277
- function getNextSearchObj(pNext) {
278
- const nextObj = {...objArray[next]};
279
- let lastField;
280
- if (comparisonFields.length > 0)
281
- lastField = comparisonFields[comparisonFields.length - 1].split(' ');
282
- else
283
- throw new Error('Supply atleast 1 comparisonFields parameter.');
284
-
285
- const lastFieldName = lastField[0];
286
- const sortDir = lastField.length > 1? lastField[1] : 'asc';
287
- const lastFieldValue = loDash.get(nextObj, lastFieldName);
288
-
289
- if (typeof lastFieldValue === 'number') {
290
- if (sortDir === 'asc')
291
- loDash.set(nextObj, lastFieldName, 1e-10 + lastFieldValue);
292
- else
293
- loDash.set(nextObj, lastFieldName, -1e-10 + lastFieldValue);
294
- }
295
- else if (typeof lastFieldValue === 'string') { // instance of String
296
- if (sortDir === 'asc')
297
- loDash.set(nextObj, lastFieldName, lastFieldValue + ' ');
298
- else
299
- loDash.set(nextObj, lastFieldName, ' ' + lastFieldValue);
300
- }
301
- else if (lastFieldValue instanceof Date) {
302
- if (sortDir === 'asc')
303
- loDash.set(nextObj, lastFieldName, new Date(1 + lastFieldValue.getTime()));
304
- else
305
- loDash.set(nextObj, lastFieldName, new Date(-1 + lastFieldValue.getTime()));
306
- }
307
- else
308
- throw new Error(`${lastFieldName} must be type Number, String or Date`);
309
-
310
- return nextObj;
311
- } // function getNextSearchObj(pNext)
312
502
 
313
503
  if (objArray.length <= 1)
314
504
  return [...objArray];
315
505
 
316
- if (![true, false].includes(firstOfDuplicates))
317
- throw new Error(`firstOfDuplicates must be one of ${[true, false]}`);
506
+ if (typeof firstOfDuplicates !== 'boolean')
507
+ throw new Error(`firstOfDuplicates must be boolean true or false.`);
318
508
 
319
509
  const noDuplicates = [];
320
-
321
- let next = 0;
322
- let nextSearchObj;
323
- if ((firstOfDuplicates)) {
324
- noDuplicates.push(objArray[next]);
325
- }
326
- nextSearchObj = getNextSearchObj(objArray[next]);
327
-
328
- while (next < objArray.length) {
329
- // The aim is to jump to the next element that is not a duplicate of objArray[next].
330
- next = binarySearchObj(objArray, nextSearchObj, next, ...comparisonFields);
331
- let comparison = objCompare(objArray[next], nextSearchObj, ...comparisonFields);
332
- if (comparison < 0) {
333
- if (firstOfDuplicates) {
334
- next++;
335
- if (next < objArray.length) {
336
- noDuplicates.push(objArray[next]);
337
- nextSearchObj = getNextSearchObj(objArray[next]);
338
- }
339
- }
340
- else {
341
- noDuplicates.push(objArray[next]);
342
- next++;
343
- if (next < objArray.length)
344
- nextSearchObj = getNextSearchObj(objArray[next]);
345
- }
346
- continue;
510
+ let idx;
511
+ let grpStart = 0; // Start index of current duplicate group.
512
+ while (grpStart < objArray.length - 1) {
513
+ if (firstOfDuplicates) {
514
+ noDuplicates.push(objArray[grpStart]);
347
515
  }
348
- else {
349
- if (!firstOfDuplicates) {
350
- noDuplicates.push(objArray[next]);
351
- }
352
- else {
353
- noDuplicates.push(objArray[next]);
354
- }
516
+
517
+ grpStart = getNextDifferent(objArray, objArray[grpStart], grpStart + 1, ...comparisonFields);
518
+ if (grpStart < 0)
519
+ break; // No more different objects.
520
+
521
+ let grpEnd = grpStart - 1;
522
+ if (!firstOfDuplicates) {
523
+ noDuplicates.push(objArray[grpEnd]);
355
524
  }
356
-
357
- nextSearchObj = getNextSearchObj(objArray[next]);
358
- next++;
359
- } // while (comparison !== 0 && next < objArray.length) {
525
+ idx = grpStart;
526
+ }
527
+
528
+ if (objCompare(objArray[idx], noDuplicates[noDuplicates.length - 1], ...comparisonFields) !== 0)
529
+ noDuplicates.push(objArray[idx]); // Add the last object.
360
530
 
361
531
  return noDuplicates;
362
532
  } // function getObjArrayWithNoDuplicates(objArray, ...comparisonFields) {
@@ -367,6 +537,9 @@ module.exports.getObjArrayWithNoDuplicates = getObjArrayWithNoDuplicates;
367
537
  * Sort directions: 'asc', 'desc'.
368
538
  * Examples: 'lastName desc', 'firstName', 'firstName asc', 'address.provinceName asc'.
369
539
  * If sort direction is not provided, then it is assumed to be ascending.
540
+ * @param {object} obj1 first object to compare
541
+ * @param {object} obj2 second object to compare
542
+ * @returns {number} a comparison value of 1, 0 or -1
370
543
  */
371
544
  function objCompare(obj1, obj2, ...comparisonFields) {
372
545
  if (comparisonFields.length === 0)
@@ -383,8 +556,8 @@ function objCompare(obj1, obj2, ...comparisonFields) {
383
556
  if (!sortDirections.includes(sortDir))
384
557
  throw new Error('Sort direction must be one of ' + sortDirections.toString());
385
558
 
386
- const value1 = loDash.get(obj1, fieldName);
387
- const value2 = loDash.get(obj2, fieldName);
559
+ const value1 = get(obj1, fieldName);
560
+ const value2 = get(obj2, fieldName);
388
561
 
389
562
  const returnValue = (sortDir === 'desc'? -1: 1);
390
563
  if (value1 > value2)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "some-common-functions-js",
3
- "version": "1.0.9",
3
+ "version": "1.1.2",
4
4
  "description": "Common functions used with Javascript objects, and field validation functions.",
5
5
  "keywords": [
6
6
  "validation",
@@ -36,8 +36,5 @@
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
  }