some-common-functions-js 1.0.1 → 1.0.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 +18 -0
  2. package/README.md +363 -54
  3. package/index.js +231 -3
  4. package/package.json +6 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Change log
2
+
3
+ ## Version 1.00 - 2025/11/19 - ITA
4
+ Genesis.
5
+
6
+ ## Version 1.01 - 2025/11/19 - ITA
7
+ Improve README.md documentation.
8
+
9
+ ## Version 1.02 - 2025/11/20 - ITA
10
+ - Improve README.md documentation and move documentation of changes to CHANGELOG.md
11
+ - Move more previously tested and used utility functions to this package. These are:
12
+ - compare(value1, value2, sortDir = 'asc')
13
+ - binarySearch(anArray, searchVal, startFrom = 0, arraySortDir = 'asc')
14
+ - objCompare(obj1, obj2, ...comparisonFields)
15
+ - binarySearchObj(objArray, searchObj, startFrom, ...sortFields)
16
+ - getObjArrayWithNoDuplicates(objArray, firstOfDuplicates, ...comparisonFields)
17
+ - Add the "lodash" dependency used by some of these newly added functions.
18
+
package/README.md CHANGED
@@ -3,73 +3,81 @@
3
3
  Common functions used for working with JavaScript objects and validating field values.
4
4
 
5
5
  ---
6
+ ## Installation
7
+ **npm install some-common-functions-js**
6
8
 
7
9
  ## 1. JavaScript Object Utilities
8
10
 
9
11
  ### `getPaths(anObject)`
10
- Returns a string array of path/field names inside a JavaScript object.
11
- ** Example **
12
- let client = {
13
- name: "Jack",
14
- surname: "Stober",
15
- address: {
16
- streetNum: "57",
17
- streetName: "Tiger Drive",
18
- suburb: "Lakeside Ext.1",
19
- town: "Lakeside",
20
- country {
21
- name: "South Africa",
22
- code: "za"
23
- }
24
- }
25
- };
26
-
27
- let paths = getPaths(client);
28
- // ["name", "surname", "address.streetNum", "address.streetName", "address.suburb",
29
- // "address.town", "address.country.name", "address.country.code"]
12
+ Returns a string array of path/field names inside a JavaScript object.
13
+ ***Example***
14
+ const { getPaths } = require("some-common-functions-js");
15
+ ```
16
+ let client = {
17
+ name: "Jack",
18
+ surname: "Stober",
19
+ address: {
20
+ streetNum: "57",
21
+ streetName: "Tiger Drive",
22
+ suburb: "Lakeside Ext.1",
23
+ town: "Lakeside",
24
+ country {
25
+ name: "South Africa",
26
+ code: "za"
27
+ }
28
+ }
29
+ };
30
30
 
31
+ let paths = getPaths(client);
32
+ // ["name", "surname", "address.streetNum", "address.streetName", "address.suburb",
33
+ // "address.town", "address.country.name", "address.country.code"]
34
+ ```
31
35
  ### `hasOnly(anObject, ...fields)`
32
- Returns `true` if the object contains **only** some or all of the specified fields and no others.
33
- ** Examples **
34
- const { hasOnly } = require("some-common-functions-js");
36
+ Returns `true` if the object contains **only** some or all of the specified fields and no others.
37
+ ***Examples***
38
+ ```
39
+ const { hasOnly } = require("some-common-functions-js");
35
40
 
36
- let car = {
37
- make: "Ford",
38
- model: "Ranger",
39
- year: "2015",
40
- };
41
+ let car = {
42
+ make: "Ford",
43
+ model: "Ranger",
44
+ year: "2015",
45
+ };
41
46
 
42
- let result = hasOnly(car, ["make", "model", "year"]);
43
- // true, because car has *only all of the specified fields and no other fields.
47
+ let result = hasOnly(car, "make", "model", "year");
48
+ // true, because car has *only all of the specified fields and no other fields.
44
49
 
45
- result = hasOnly(car, ["make", "model", "year", "maxSpeed", "gvm"]);
46
- // true, because car has *only some of the specified fields and no other fields.
50
+ result = hasOnly(car, "make", "model", "year", "maxSpeed", "gvm");
51
+ // true, because car has *only some of the specified fields and no other fields.
47
52
 
48
- result = hasOnly(car, ["maxSpeed", "gvm", "power"]);
49
- // false, because car has fields other than the specified fields.
53
+ result = hasOnly(car, "maxSpeed", "gvm", "power");
54
+ // false, because car has fields other than the specified fields.
50
55
 
51
- result = hasOnly(car, ["make", "model"]);
52
- // false, because car has fields other than the specified fields.
56
+ result = hasOnly(car, "make", "model");
57
+ // false, because car has fields other than the specified fields.
58
+ ```
53
59
 
54
60
  ### `hasAll(anObject, ...fields)`
55
61
  Returns `true` if the object contains **all** the specified fields.
56
- The object may contain additional fields.
57
- ** Examples **
58
- const { hasAll } = require("some-common-functions-js");
59
-
60
- let car = {
61
- make: "Ford",
62
- model: "Ranger",
63
- year: "2015",
64
- power: "1000kW",
65
- type: "pickup truck"
66
- };
67
-
68
- let result = hasAll(car, ["make", "model"]);
69
- // true, because car has all the specified fields.
70
-
71
- let result = hasAll(car, ["passengerCapacity", "year"]);
72
- // false, because car does not have "passengerCapacity" field.
62
+ The object may contain additional fields.
63
+ ***Examples***
64
+ ```
65
+ const { hasAll } = require("some-common-functions-js");
66
+
67
+ let car = {
68
+ make: "Ford",
69
+ model: "Ranger",
70
+ year: "2015",
71
+ power: "1000kW",
72
+ type: "pickup truck"
73
+ };
74
+
75
+ let result = hasAll(car, "make", "model");
76
+ // true, because car has all the specified fields.
77
+
78
+ let result = hasAll(car, "passengerCapacity", "year");
79
+ // false, because car does not have "passengerCapacity" field.
80
+ ```
73
81
  ---
74
82
 
75
83
  ## 2. Field Validation Functions
@@ -92,7 +100,308 @@ Returns `true` if an organisation name is valid.
92
100
  ### `isValidPassword(password)`
93
101
  Returns `true` if the password meets the required strength rules.
94
102
 
103
+ ## 3. Comparison and Binary Search functions for primitive types and javascript objects
104
+
105
+ ### `compare(value1, value2, sortDir = 'asc')`
106
+ Compares two values of the same primitive type, according to the sort direction.
107
+ * A return value of -1 means that value1 is before value2 in terms of sort order.
108
+ * A return value of 1 means that value1 is after value2 in terms of sort order.
109
+ * A return value of 0 means that value1 is equal to value2.
110
+ * Sort directions: 'asc', 'desc'. Default is 'asc'.
111
+
112
+ ***Examples***
113
+ const { compare } = require("some-common-functions-js");
114
+
115
+ let x = "Fong Kong";
116
+ let y = "Isaiah Tshabalala";
117
+ let result = compare(x, y); // -1 because "Fong Kong" is before "Isaiah Tshabalala" in ascending order.
118
+ result = compare(y, x);
119
+ console.log(result); // 1 because "Isaiah Tshabalala" is after "Fong Kong" in ascending order.
120
+
121
+ result = compare(x, y, 'desc');
122
+ console.log(result); // 1 because "Fong Kong" is after "Isaiah Tshabalala" in descending order.
123
+
124
+ ### `binarySearch(anArray, searchVal, startFrom = 0, arraySortDir = 'asc')`
125
+ Binary Searches a sorted primitive type array for a value and returns the index.
126
+ * ArraySortDir specifies the direction in which the array is sorted (desc or asc).
127
+ * If the array contains the value searched for, then the index returned is the location of this value on the array,
128
+ * otherwise, the index is of closest value in the array that is before or after the search value in terms of sort order.
129
+ * The programmer must write equality-check code to determine if the array element at the returned index is indeed the sought element.
130
+ * Value of -1 is returned for an empty array.
131
+ * This function is to be used also in cases where values are to be inserted into the array while maintaining sort order.
132
+ * Sort directions: 'asc', 'desc'. Default is 'asc'.
133
+
134
+ ***Example***
135
+ const { binarySearch, compare } = require("some-common-functions-js");
136
+ let myArray = [100, 101, 102, 103, 104, 105, 106, 107];
137
+ let index = binarySearch(myArray, 103); // 3
138
+ let result = compare(103, myArray[index]); // 0, 103 === myArray[index].
139
+
140
+ index = binarySearch(myArray, 103, 4); // 4
141
+ result = compare(103, myArray[4]); // -1 meaning 103 is less than (before) myArray[4]
142
+
143
+ ### `objCompare(obj1, obj2, ...comparisonFields)`
144
+ Compare 2 objects according to the comparison fields specified in the comparison fields, and return the result.
145
+ * Each each of the comparisonFields must be of the form 'fieldName sortDirection' or 'fieldName'.
146
+ * Sort directions: 'asc', 'desc'. Default: 'asc'.
147
+ * Examples: 'lastName desc', 'firstName', 'firstName asc', 'address.provinceName asc'.
148
+ * If sort direction is not provided, then it is assumed to be ascending.
149
+
150
+ ***Example***
151
+ ```
152
+ const { objCompare } = require('some-common-functions-js');
153
+
154
+ let anObject = {
155
+ firstName: "Isaiah",
156
+ lastName: "Tshabalala",
157
+ address: {
158
+ houseNum: "23-B",
159
+ streetName: "Main Road",
160
+ mainPlace: "Lakeside",
161
+ subPlace: "Lakeside Ext.1",
162
+ city: "Johannesburg",
163
+ country: {
164
+ name: "South Africa",
165
+ code: "ZA"
166
+ }
167
+ }
168
+ };
169
+
170
+ let anObject2 = {
171
+ firstName: "Lindiwe",
172
+ lastName: "Tshabalala",
173
+ address: {
174
+ houseNum: "5520",
175
+ streetName: "Main Road",
176
+ mainPlace: "Evaton",
177
+ subPlace: "Evaton Small Farms",
178
+ city: "Vereeniging",
179
+ country: {
180
+ name: "South Africa",
181
+ code: "ZA"
182
+ }
183
+ }
184
+ };
185
+
186
+ let result = objCompare(anObject, anObject2, "lastName", "firstName");
187
+ // -1 because "Tshabalala Isaiah" is before "Tshabalala Lindiwe" according to ascending order.
188
+
189
+ result objCompare(anObject, anObject2, "address.country.name", "address.city desc");
190
+ // 1 because same country (0), but "Johannesburg" is after "Vereeniging" according to descending order.
191
+
192
+ let anObject3 = {
193
+ firstName: "Huang",
194
+ lastName: "Shi",
195
+ address: {
196
+ houseNum: "5520",
197
+ streetName: "Yuan Road",
198
+ mainPlace: "Qīngyún Chéng",
199
+ subPlace: "Phase 1",
200
+ city: "Dragon City",
201
+ country: {
202
+ name: "China",
203
+ code: "CN"
204
+ }
205
+ }
206
+ };
207
+
208
+ let objArray = [anObject2, anObject, anObject3];
209
+ // Using objCompare to sort objects in objArray using multiple fields including nested fields.
210
+ objArray.sort((obj1, obj2)=> {
211
+ return commonFunctions.objCompare(
212
+ obj1, obj2,
213
+ "address.country.name",
214
+ "address.city",
215
+ "adress.mainPlace",
216
+ "address.subPlace",
217
+ "lastName",
218
+ "firstName"
219
+ );
220
+ });
221
+ console.log(objArray);
222
+
223
+ /*
224
+ [
225
+ {
226
+ firstName: 'Huang',
227
+ lastName: 'Shi',
228
+ address: {
229
+ houseNum: '5520',
230
+ streetName: 'Yuan Road',
231
+ mainPlace: 'Qīngyún Chéng',
232
+ subPlace: 'Phase 1',
233
+ city: 'Dragon City',
234
+ country: {
235
+ name: "China",
236
+ code: "CN"
237
+ }
238
+ }
239
+ },
240
+ {
241
+ firstName: 'Albertina',
242
+ lastName: 'Tshabalala',
243
+ address: {
244
+ houseNum: '5520',
245
+ streetName: 'Main Road',
246
+ mainPlace: 'Evaton',
247
+ subPlace: 'Evaton Small Farms',
248
+ city: 'Vereeniging',
249
+ country: {
250
+ name: "South Africa",
251
+ code: "ZA"
252
+ }
253
+ }
254
+ },
255
+ {
256
+ firstName: 'Isaiah',
257
+ lastName: 'Tshabalala',
258
+ address: {
259
+ houseNum: '5520',
260
+ streetName: 'Main Road',
261
+ mainPlace: 'Evaton',
262
+ subPlace: 'Evaton Small Farms',
263
+ city: 'Vereeniging',
264
+ country: {
265
+ name: "South Africa",
266
+ code: "ZA"
267
+ }
268
+ }
269
+ }
270
+ ]
271
+ */
272
+
273
+ let teams = [
274
+ {
275
+ score: 85,
276
+ numGames: 10
277
+ },
278
+ {
279
+ score: 90,
280
+ numGames: 12
281
+ },
282
+ {
283
+ score: 85,
284
+ numGames: 8
285
+ },
286
+ {
287
+ score: 90,
288
+ numGames: 10
289
+ }
290
+ ];
291
+ // Using objCompare to sort fields where there
292
+ teams.sort((team1, team2) => {
293
+ return commonFunctions.objCompare(
294
+ team1, team2,
295
+ "score desc",
296
+ "numGames asc"
297
+ );
298
+ });
299
+ console.log(teams);
300
+ /*
301
+
302
+ let teams = [
303
+ {
304
+ score: 85,
305
+ numGames: 10
306
+ },
307
+ {
308
+ score: 90,
309
+ numGames: 12
310
+ },
311
+ {
312
+ score: 85,
313
+ numGames: 8
314
+ },
315
+ {
316
+ score: 90,
317
+ numGames: 10
318
+ }
319
+ ];
320
+ // Sort by score descending, then by numGames ascending.
321
+ objArray.sort((team1, team2) => {
322
+ return commonFunctions.objCompare(
323
+ team1, team2,
324
+ "score desc",
325
+ "numGames asc"
326
+ );
327
+ });
328
+
329
+ /*
330
+ [
331
+ { score: 90, numGames: 10 },
332
+ { score: 90, numGames: 12 },
333
+ { score: 85, numGames: 8 },
334
+ { score: 85, numGames: 10 }
335
+ ]
336
+ */
337
+ ```
338
+ ### `binarySearchObj(objArray, searchObj, startFrom, ...sortFields)`
339
+ Binary Search the sorted (ascending or descending order) array of objects for a value and return the index.
340
+ * The assumption is that the array is sorted in order of 1 or more sort fields,
341
+ * for example 'lastName asc', 'firstName', 'address.province asc', 'address.townOrCity asc'.
342
+ * If sort direction is not provided, then it is assumed to be 'asc' (ascending).
343
+ * If the array contains the object with values searched for, then the index returned is the location of this value in the array,
344
+ * otherwise, the index is of the closest value in the array that is before or after the searchObj value.
345
+ * Return -1 for an empty array.
346
+ * Assumed field data types are Number, String and Date.
347
+ * This function is to be used also in cases where objects are to be inserted into the array while maintaining sort order.
348
+
349
+ ***Example***
350
+ ```
351
+ let teamsArray = [
352
+ { score: 90, numGames: 10 },
353
+ { score: 90, numGames: 12 },
354
+ { score: 85, numGames: 8 },
355
+ { score: 85, numGames: 10 }
356
+ ]; // Sorted by "score desc", "numGames asc".
357
+
358
+
359
+ let searchObj = { score: 85, numGames: 8 };
360
+ let anIndex = (commonFunctions.binarySearchObj(teamsArray, searchObj, "score desc", "numGames asc"));
361
+
362
+ let result = commonFunctions.objCompare(searchObj, teamsArray[anIndex], "score desc", "numGames asc"); // 0 -- an object with value { score: 85, numGames: 8} exists at teamsArray[anIndex];
363
+ ```
364
+
365
+ ## 4. `getObjArrayWithNoDuplicates(objArray, firstOfDuplicates, ...comparisonFields)`
366
+ 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 comparisonFields.
367
+ * If firstOfDuplicates === true, then the first element in each set of duplicates is taken.
368
+ * if firstOfDuplicates === false, then the last element is taken from each set of duplicates.
369
+ * Assumed field data types are Number, String and Date.
370
+ * The array must be sorted according to the comparison fields before calling this function.
371
+ * The value of the comparison field must include both the field name and sort direction.
372
+ * Sort direction assumed to be "asc" if not provided.
373
+ * Examples of comparison fields: "firstName", "lastName desc", "address.province asc", "address.townOrCity".
374
+
375
+ ***Example***
376
+ ```
377
+ let teamsArray = [
378
+ { score: 90, numGames: 10 },
379
+ { score: 90, numGames: 10 },
380
+ { score: 90, numGames: 10 },
381
+ { score: 90, numGames: 12 },
382
+ { score: 90, numGames: 12 },
383
+ { score: 90, numGames: 12 },
384
+ { score: 85, numGames: 8 },
385
+ { score: 85, numGames: 8 },
386
+ { score: 85, numGames: 10 },
387
+ { score: 85, numGames: 10 },
388
+ { score: 85, numGames: 10 }
389
+ ]; // Sorted by "score desc", "numGames asc".
390
+
391
+ let noDuplicatesArray = commonFunctions.getObjArrayWithNoDuplicates(teamsArray, true, "score desc", "numGames asc");
392
+ console.log(noDuplicatesArray); // Should contain only unique objects according to comparison fields.
393
+ /*
394
+ [
395
+ { score: 90, numGames: 10 },
396
+ { score: 90, numGames: 12 },
397
+ { score: 85, numGames: 8 },
398
+ { score: 85, numGames: 10 }
399
+ ]
400
+ */
401
+ ```
402
+
95
403
  ---
96
404
 
97
405
  ## License
98
- MIT
406
+ MIT
407
+
package/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Date Dev Version Description
4
4
  * 2025/11/19 ITA 1.00 Genesis.
5
5
  */
6
-
6
+ const loDash = require('lodash');
7
7
 
8
8
  function isValidUserName(userName) {
9
9
  if (!userName) // The username must be provided.
@@ -85,7 +85,7 @@ module.exports.isValidPassword = isValidPassword;
85
85
  /**
86
86
  * Get the paths (fields) of the plain Javascript object.
87
87
  * @param {object} anObject
88
- * @returns a string array of paths.
88
+ * @returns a sorted string array of paths.
89
89
  */
90
90
  function getPaths(anObject) {
91
91
  const paths = [];
@@ -155,4 +155,232 @@ function hasAll(anObject, ...fields) {
155
155
 
156
156
  return (count === fields.length);
157
157
  }
158
- module.exports.hasAll = hasAll;
158
+ module.exports.hasAll = hasAll;
159
+
160
+
161
+ /**Binary Search the sorted primitive data array for a value and return the index.
162
+ * ArraySortDir specifies the direction in which the array is sorted (desc or asc).
163
+ * If the array contains the value searched for, then the index returned is the location of this value on the array,
164
+ * otherwise, the index is of closest value in the array that is before or after the search value in terms of sort order.
165
+ * Return -1 for an empty array.
166
+ * This function is to be used also in cases where values are to be inserted into the array while maintaining sort order.
167
+ */
168
+ function binarySearch(anArray, searchVal, startFrom = 0, arraySortDir = 'asc') {
169
+
170
+ const sortDirections = ['asc', 'desc']
171
+ if (!['asc', 'desc'].includes(arraySortDir))
172
+ throw new Error(`arraySortDir must be one of ${sortDirections}`);
173
+
174
+ if (anArray.length === 0)
175
+ return -1; // Empty array.
176
+
177
+ let start = startFrom,
178
+ end = anArray.length - 1;
179
+
180
+ while(start < end) {
181
+ if (compare(anArray[start], searchVal) === 0)
182
+ return start;
183
+ else if (compare(anArray[end], searchVal) === 0)
184
+ return end;
185
+
186
+ const mid = Math.trunc((start + end) / 2);
187
+ const comparison = compare(anArray[mid], searchVal, arraySortDir);
188
+ if (comparison < 0)
189
+ start = mid + 1;
190
+ else if (comparison > 0)
191
+ end = mid - 1;
192
+ else
193
+ return mid;
194
+ } // while(start < end) {
195
+
196
+ return start;
197
+ } // function binarySearch(anArray, arraySortDir, searchVal) {
198
+ module.exports.binarySearch = binarySearch;
199
+
200
+ /** Compare two values of the same primitive type, according to the sort direction.
201
+ * A return value of -1 means that value1 is before value2 in terms of sort order.
202
+ * A return value of 1 means that value1 is after value2 in terms of sort order.
203
+ * A return value of 0 means that value1 is equal to value2.
204
+ * Sort directions: 'asc', 'desc'. Default is 'asc'.
205
+ */
206
+ function compare(value1, value2, sortDir = 'asc') {
207
+ if (!['asc', 'desc'].includes(sortDir))
208
+ throw new Error(`sortDir must be one of ${sortDir}`);
209
+
210
+ const returnValue = (sortDir === 'desc'? -1 : 1);
211
+ if (value1 > value2)
212
+ return returnValue;
213
+ else if (value1 < value2)
214
+ return -returnValue;
215
+ else // Avoid if (value1 === value2) because this may yield false for reference types (ie. Dates), because of different memory addresses.
216
+ return 0;
217
+ } // function compare(value1, value2, sortDir) {
218
+ module.exports.compare = compare;
219
+
220
+ /**Binary Search the sorted (ascending or descending order) array of objects for a value and return the index.
221
+ * The assumption is that the array is sorted in order of 1 or more sort fields,
222
+ * for example'lastName asc', 'firstName', 'address.province asc', 'address.townOrCity asc'.
223
+ * If the array contains the object with values searched for, then the index returned is the location of this value in the array,
224
+ * otherwise, the index is of the closest value in the array that is before or after the searchObj value.
225
+ * Return -1 for an empty array.
226
+ * Assumed field data types are Number, String and Date.
227
+ * This function is to be used also in cases where objects are to be inserted into the array while maintaining sort order.
228
+ */
229
+ function binarySearchObj(objArray, searchObj, startFrom, ...sortFields) {
230
+ if (objArray.length === 0)
231
+ return -1;
232
+
233
+ let start = startFrom,
234
+ end = objArray.length - 1;
235
+
236
+ while(start < end) {
237
+ if (objCompare(objArray[start], searchObj, ...sortFields) === 0)
238
+ return start;
239
+ else if (objCompare(objArray[end], searchObj, ...sortFields) === 0)
240
+ return end;
241
+
242
+ let mid = Math.trunc((start + end) / 2);
243
+
244
+ if (objCompare(objArray[mid], searchObj, ...sortFields) < 0)
245
+ start = mid + 1;
246
+ else if (objCompare(objArray[mid], searchObj, ...sortFields) > 0)
247
+ end = mid - 1;
248
+ else
249
+ return mid;
250
+ } // while(start < end) {
251
+
252
+ return start;
253
+ } // function binarySearchObj(objArray, searchObj, ...comparisonFields) {
254
+ module.exports.binarySearchObj = binarySearchObj;
255
+
256
+ /**Create an array with duplicates eliminated. Taking only the first or last object from each duplicate set.
257
+ * If firstOfDuplicates === true, then the first element in each set of duplicates is taken.
258
+ * if firstOfDuplicates === false, then the last element is taken from each set of duplicates.
259
+ * Assumed field data types are Number, String and Date.
260
+ * The array must be sorted according to the comparison fields before calling this function.
261
+ * The value of the comparison field must include both the field name and sort direction.
262
+ * Sort direction assumed to be "asc" if not provided.
263
+ * Examples of comparison fields: "firstName", "lastName desc", "address.province asc", "address.townOrCity".
264
+ */
265
+ function getObjArrayWithNoDuplicates(objArray, firstOfDuplicates, ...comparisonFields) {
266
+ function getNextSearchObj(pNext) {
267
+ const nextObj = {...objArray[next]};
268
+ let lastField;
269
+ if (comparisonFields.length > 0)
270
+ lastField = comparisonFields[comparisonFields.length - 1].split(' ');
271
+ else
272
+ throw new Error('Supply atleast 1 comparisonFields parameter.');
273
+
274
+ const lastFieldName = lastField[0];
275
+ const sortDir = lastField.length > 1? lastField[1] : 'asc';
276
+ const lastFieldValue = loDash.get(nextObj, lastFieldName);
277
+
278
+ if (typeof lastFieldValue === 'number') {
279
+ if (sortDir === 'asc')
280
+ loDash.set(nextObj, lastFieldName, 1e-10 + lastFieldValue);
281
+ else
282
+ loDash.set(nextObj, lastFieldName, -1e-10 + lastFieldValue);
283
+ }
284
+ else if (typeof lastFieldValue === 'string') { // instance of String
285
+ if (sortDir === 'asc')
286
+ loDash.set(nextObj, lastFieldName, lastFieldValue + ' ');
287
+ else
288
+ loDash.set(nextObj, lastFieldName, ' ' + lastFieldValue);
289
+ }
290
+ else if (lastFieldValue instanceof Date) {
291
+ if (sortDir === 'asc')
292
+ loDash.set(nextObj, lastFieldName, new Date(1 + lastFieldValue.getTime()));
293
+ else
294
+ loDash.set(nextObj, lastFieldName, new Date(-1 + lastFieldValue.getTime()));
295
+ }
296
+ else
297
+ throw new Error(`${lastFieldName} must be type Number, String or Date`);
298
+
299
+ return nextObj;
300
+ } // function getNextSearchObj(pNext)
301
+
302
+ if (objArray.length <= 1)
303
+ return [...objArray];
304
+
305
+ if (![true, false].includes(firstOfDuplicates))
306
+ throw new Error(`firstOfDuplicates must be one of ${[true, false]}`);
307
+
308
+ const noDuplicates = [];
309
+
310
+ let next = 0;
311
+ let nextSearchObj;
312
+ if ((firstOfDuplicates)) {
313
+ noDuplicates.push(objArray[next]);
314
+ }
315
+ nextSearchObj = getNextSearchObj(objArray[next]);
316
+
317
+ while (next < objArray.length) {
318
+ // The aim is to jump to the next element that is not a duplicate of objArray[next].
319
+ next = binarySearchObj(objArray, nextSearchObj, next, ...comparisonFields);
320
+ let comparison = objCompare(objArray[next], nextSearchObj, ...comparisonFields);
321
+ if (comparison < 0) {
322
+ if (firstOfDuplicates) {
323
+ next++;
324
+ if (next < objArray.length) {
325
+ noDuplicates.push(objArray[next]);
326
+ nextSearchObj = getNextSearchObj(objArray[next]);
327
+ }
328
+ }
329
+ else {
330
+ noDuplicates.push(objArray[next]);
331
+ next++;
332
+ if (next < objArray.length)
333
+ nextSearchObj = getNextSearchObj(objArray[next]);
334
+ }
335
+ continue;
336
+ }
337
+ else {
338
+ if (!firstOfDuplicates) {
339
+ noDuplicates.push(objArray[next]);
340
+ }
341
+ else {
342
+ noDuplicates.push(objArray[next]);
343
+ }
344
+ }
345
+
346
+ nextSearchObj = getNextSearchObj(objArray[next]);
347
+ next++;
348
+ } // while (comparison !== 0 && next < objArray.length) {
349
+
350
+ return noDuplicates;
351
+ } // function getObjArrayWithNoDuplicates(objArray, ...comparisonFields) {
352
+ module.exports.getObjArrayWithNoDuplicates = getObjArrayWithNoDuplicates;
353
+
354
+ /**Compare 2 objects according to the comparison fields specified in the comparison fields, and return the result.
355
+ * Each each of the comparisonFields must be of the form 'fieldName sortDirection' or 'fieldName'.
356
+ * Sort directions: 'asc', 'desc'.
357
+ * Examples: 'lastName desc', 'firstName', 'firstName asc', 'address.provinceName asc'.
358
+ * If sort direction is not provided, then it is assumed to be ascending.
359
+ */
360
+ function objCompare(obj1, obj2, ...comparisonFields) {
361
+ if (comparisonFields.length === 0)
362
+ throw new Error('comparisonFields not supplied!');
363
+
364
+ const sortDirections = ['', 'asc', 'desc'];
365
+ for (const index in comparisonFields) {
366
+ const field = comparisonFields[index].split(' ');
367
+ const fieldName = field[0];
368
+ let sortDir = '';
369
+ if (field.length === 2)
370
+ sortDir = field[1];
371
+
372
+ if (!sortDirections.includes(sortDir))
373
+ throw new Error('Sort direction must be one of ' + sortDirections.toString());
374
+
375
+ const value1 = loDash.get(obj1, fieldName);
376
+ const value2 = loDash.get(obj2, fieldName);
377
+
378
+ const returnValue = (sortDir === 'desc'? -1: 1);
379
+ if (value1 > value2)
380
+ return returnValue;
381
+ else if (value1 < value2)
382
+ return -returnValue;
383
+ } // for (const field in comparisonFields) {
384
+ return 0;
385
+ } // function comparison(obj1, obj2, ...comparisonFields) {
386
+ module.exports.objCompare = objCompare;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "some-common-functions-js",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Common functions used with Javascript objects, and field validation functions.",
5
5
  "keywords": [
6
6
  "validation",
@@ -12,6 +12,8 @@
12
12
  "password validation",
13
13
  "phone validation",
14
14
  "username validation",
15
+ "object comparison",
16
+ "comparison",
15
17
  "javascript",
16
18
  "utilities",
17
19
  "utility",
@@ -31,5 +33,8 @@
31
33
  "main": "index.js",
32
34
  "scripts": {
33
35
  "test": "echo \"Error: no test specified\" && exit 1"
36
+ },
37
+ "dependencies": {
38
+ "lodash": "^4.17.21"
34
39
  }
35
40
  }