some-common-functions-js 1.1.0 → 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 +9 -1
  2. package/README.md +125 -27
  3. package/index.js +122 -85
  4. package/package.json +1 -4
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, by ordered by field name ascending.
41
- ***Examples***
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,66 @@ 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
+
78
137
  ### `hasOnly(anObject, ...fields)`
79
138
  Returns `true` if the object contains **only** some or all of the specified fields and no others.
139
+
80
140
  ***Examples***
81
- ```
141
+ ```
82
142
  const { hasOnly } = require("some-common-functions-js");
83
143
 
84
144
  let car = {
@@ -97,12 +157,14 @@ result = hasOnly(car, "maxSpeed", "gvm", "power");
97
157
 
98
158
  result = hasOnly(car, "make", "model");
99
159
  // false, because car has fields other than the specified fields.
100
- ```
160
+ ```
161
+
101
162
  ### `hasAll(anObject, ...fields)`
102
163
  Returns `true` if the object contains **all** the specified fields.
103
164
  The object may contain additional fields.
165
+
104
166
  ***Examples***
105
- ```
167
+ ```
106
168
  const { hasAll } = require("some-common-functions-js");
107
169
  let car = {
108
170
  make: "Ford",
@@ -116,11 +178,13 @@ let result = hasAll(car, "make", "model");
116
178
 
117
179
  let result = hasAll(car, "passengerCapacity", "year");
118
180
  // false, because car does not have "passengerCapacity" field.
119
- ```
181
+ ```
182
+
120
183
  ### `hasOnlyAll(anObject, ...fields)`
121
- Return `true` if an object contains only all the specified fields, nothing more, nothing less
122
- ***Example***
123
- ```
184
+ Return `true` if an object contains only all the specified fields, nothing more, nothing less
185
+
186
+ ***Examples***
187
+ ```
124
188
  const { hasOnlyAll } = require("some-common-functions-js");
125
189
  let car = {
126
190
  make: "Ford",
@@ -327,20 +391,16 @@ console.log(objArray);
327
391
  */
328
392
  let teams = [
329
393
  {
330
- score: 85,
331
- numGames: 10
394
+ score: 85, numGames: 10
332
395
  },
333
396
  {
334
- score: 90,
335
- numGames: 12
397
+ score: 90, numGames: 12
336
398
  },
337
399
  {
338
- score: 85,
339
- numGames: 8
400
+ score: 85, numGames: 8
340
401
  },
341
402
  {
342
- score: 90,
343
- numGames: 10
403
+ score: 90, numGames: 10
344
404
  }
345
405
  ];
346
406
  // Using objCompare to sort fields where there are mixed sort directions.
@@ -396,7 +456,7 @@ Create an array of objects with duplicates eliminated. Taking only the first or
396
456
  * The value of the comparison field must include both the field name and sort direction.
397
457
  * Sort direction assumed to be "asc" if not provided.
398
458
  * Examples of comparison fields: "firstName", "lastName desc", "address.province asc", "address.townOrCity".
399
-
459
+
400
460
  ***Example***
401
461
  ```
402
462
  const { getObjArrayWithNoDuplicates } = require("some-common-functions-js");
@@ -424,7 +484,42 @@ console.log(noDuplicatesArray); // Should contain only unique objects according
424
484
  { score: 85, numGames: 10 }
425
485
  ]
426
486
  */
427
- ```
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
+
428
523
  ## 4. Date Timestamp Functions
429
524
  ### `timeStampYyyyMmDd(dateInstance)`
430
525
  Converts the date object to a string of the form CCYY-MM-DD
@@ -432,6 +527,9 @@ Converts the date object to a string of the form CCYY-MM-DD
432
527
  ### `timeStampString(dateInstance)`
433
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'
434
529
 
530
+ ### `addLeadingZeros(aNumber, newLength)`
531
+ Add leading zeros to a numerical string. E.g. addLeadingZeros(9, 3) = '009'
532
+
435
533
  ---
436
534
  ## License
437
535
  MIT
package/index.js CHANGED
@@ -4,8 +4,9 @@
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. * .
7
9
  */
8
- const loDash = require('lodash');
9
10
 
10
11
  /**Return true if userName is valid
11
12
  * @param {string} userName
@@ -203,27 +204,87 @@ function getPaths(anObject) {
203
204
  } // function getPaths()
204
205
  module.exports.getPaths = getPaths;
205
206
 
206
- /** Return an object with sorted fields, by ordered by field name ascending.
207
+ /** Return an object with sorted fields, ordered by field name ascending.
207
208
  * This is desirable when equality comparison is done to ensure two objects sharing equal field values
208
209
  * the pass the equality test stringify(object1) === stringify(object2)
209
210
  * @param {object} pObject
210
211
  * @returns {object} an object with fields sorted in ascending order of field names.
211
212
  */
212
213
  function getSortedObject(pObject) {
214
+ const objClone = deepClone(pObject);
215
+ const paths = [];
216
+ const sortedObject = {};
213
217
 
214
- const paths = getPaths(pObject);
218
+ // Obtain the outermost fields and sort them.
219
+ for (let field in objClone) {
220
+ paths.push(field);
221
+ }
215
222
  paths.sort();
216
- const sortedObject = {};
217
223
 
224
+ // Assign the sorted fields to the new object.
218
225
  for (let index in paths) {
219
- const path = paths[index];
220
- const value = loDash.get(pObject, path);
221
- loDash.set(sortedObject, path, value);
222
- } // for (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
+
223
235
  return sortedObject;
224
236
  } // function getSortedObject(pObject) {
225
237
  module.exports.getSortedObject = getSortedObject;
226
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
+
227
288
  /**
228
289
  * Determine whether an object contains only 1, some or all of the specified fields, and not any other fields.
229
290
  * @param {object} anObject a Javascript object.
@@ -393,6 +454,37 @@ function binarySearchObj(objArray, searchObj, startFrom = 0, ...sortFields) {
393
454
  } // function binarySearchObj(objArray, searchObj, ...comparisonFields) {
394
455
  module.exports.binarySearchObj = binarySearchObj;
395
456
 
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
+
396
488
  /**Create an array with duplicates eliminated, according to certain fields. Taking only the first or last object from each duplicate set.
397
489
  * If firstOfDuplicates === true, then the first element in each set of duplicates is taken.
398
490
  * if firstOfDuplicates === false, then the last element is taken from each set of duplicates.
@@ -407,89 +499,34 @@ module.exports.binarySearchObj = binarySearchObj;
407
499
  * @returns {Array<object>} an array with no duplicates.
408
500
  */
409
501
  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
502
 
446
503
  if (objArray.length <= 1)
447
504
  return [...objArray];
448
505
 
449
- if (![true, false].includes(firstOfDuplicates))
450
- 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.`);
451
508
 
452
509
  const noDuplicates = [];
453
-
454
- let next = 0;
455
- let nextSearchObj;
456
- if ((firstOfDuplicates)) {
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;
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]);
480
515
  }
481
- else {
482
- if (!firstOfDuplicates) {
483
- noDuplicates.push(objArray[next]);
484
- }
485
- else {
486
- noDuplicates.push(objArray[next]);
487
- }
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]);
488
524
  }
489
-
490
- nextSearchObj = getNextSearchObj(objArray[next]);
491
- next++;
492
- } // 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.
493
530
 
494
531
  return noDuplicates;
495
532
  } // function getObjArrayWithNoDuplicates(objArray, ...comparisonFields) {
@@ -519,8 +556,8 @@ function objCompare(obj1, obj2, ...comparisonFields) {
519
556
  if (!sortDirections.includes(sortDir))
520
557
  throw new Error('Sort direction must be one of ' + sortDirections.toString());
521
558
 
522
- const value1 = loDash.get(obj1, fieldName);
523
- const value2 = loDash.get(obj2, fieldName);
559
+ const value1 = get(obj1, fieldName);
560
+ const value2 = get(obj2, fieldName);
524
561
 
525
562
  const returnValue = (sortDir === 'desc'? -1: 1);
526
563
  if (value1 > value2)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "some-common-functions-js",
3
- "version": "1.1.0",
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
  }