jupyter-ijavascript-utils 1.38.0 → 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DOCS.md CHANGED
@@ -75,7 +75,9 @@ Give it a try here:
75
75
 
76
76
  ## What's New
77
77
 
78
- * 1.38 - {@link module:array.extract|array.extract} / {@link module:array.apply|array.apply} and {@link module:object.extract|object.extract} / {@link module:object.apply|object.apply}
78
+ * 1.40 - {@link module:array.extract|array.extract} and {@link module:array.applyArrayValues|array.applyArrayValues} to allow for extracting values from arrays, transforming them on a separate process and applying them deeply and safely
79
+ * 1.39 - {@link module:format.extractWords|format.exportWords} - to identify distinct words in strings using unicode character properties
80
+ * 1.38 - {@link module:object.extractObjectProperty|object.extractObjectProperty} / {@link module:object.applyPropertyValue|object.applyPropertyValue} to allow for extracting values from arrays, transforming them on a separate process and applying them back
79
81
  * 1.37 - {@link module:format.replaceString|format.replaceString} as convenience for replacing only a single string.
80
82
  * 1.36 - {@link module:format.replaceStrings|format.replaceStrings} to allow for replacement dictionaries and tuplets
81
83
  * 1.35 - {@link module:object.extractObjectProperties|extractObjectProperties} / {@link module:object.extractObjectProperty|extractObjectProperty} - to do horizontal transposes on objects
package/Dockerfile CHANGED
@@ -1,3 +1,3 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
- FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.38.0
3
+ FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.40.0
package/README.md CHANGED
@@ -55,7 +55,9 @@ This is not intended to be the only way to accomplish many of these tasks, and a
55
55
 
56
56
  # What's New
57
57
 
58
- * 1.38 - array.extract / array.apply and object.extract / object.apply
58
+ * 1.40 - array.extract and array.applyArrayValue to allow for extracting values from arrays, transforming them on a separate process and applying them back
59
+ * 1.39 - format.exportWords - to identify distinct words in strings using unicode character properties
60
+ * 1.38 - object.extract / object.apply
59
61
  * 1.37 - {@link module:format.replaceString|format.replaceString} as convenience for replacing only a single string.
60
62
  * 1.36 - replaceStrings to allow for replacement dictionaries and tuplets
61
63
  * 1.35 - object.extractObjectProperties / object.extractObjectProperty - to do horizontal transposes on objects
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.38.0",
3
+ "version": "1.40.0",
4
4
  "description": "Utilities for working with iJavaScript - a Jupyter Kernel",
5
5
  "homepage": "https://jupyter-ijavascript-utils.onrender.com/",
6
6
  "license": "MIT",
package/src/array.js CHANGED
@@ -27,6 +27,10 @@ require('./_types/global');
27
27
  * * {@link module:array.pickRows|array.pickRows} - picks a row from a 2d array
28
28
  * * {@link module:array.pickColumns|array.pickColumns} - picks a column from a 2d array
29
29
  * * {@link module:array.pick|array.pick} - picks either/or rows and columns
30
+ * * {@link module:array.extract|array.extract} - synonym to array.pick to pick either a row or column from an array
31
+ * * Applying a value
32
+ * * {@link module:array.applyArrayValue|array.applyArrayValue} - applies a value deeply into an array safely
33
+ * * {@link module:array.applyArrayValues|array.applyArrayValues} - applies a value / multiple values deeply into an array safely
30
34
  * * Understanding Values
31
35
  * * {@link module:array.isMultiDimensional|array.isMultiDimensional} - determines if an array is multi-dimensional
32
36
  *
@@ -150,6 +154,7 @@ module.exports.peekLast = function peekLast(targetArray, defaultVal = null) {
150
154
  * @param {Array} array2d - 2d array to pick from [row][column]
151
155
  * @param {...Number} rowIndices - Indexes of the row to return, [0...length-1]
152
156
  * @returns - Array with only those rows
157
+ * @see {@link module:array.pick|array.pick} - pick either rows or columns
153
158
  * @example
154
159
  * data = [
155
160
  * ['john', 23, 'purple'],
@@ -179,6 +184,7 @@ module.exports.pickRows = function pickRows(array2d, ...rowIndices) {
179
184
  * @param {Array} array2d - 2d array to pick from [row][column]
180
185
  * @param {...any} columns - Indexes of the columns to pick the values from: [0...row.length-1]
181
186
  * @returns - Array with all rows, and only those columns
187
+ * @see {@link module:array.pick|array.pick} - pick either rows or columns
182
188
  * @example
183
189
  * data = [
184
190
  * ['john', 23, 'purple'],
@@ -210,8 +216,9 @@ module.exports.pickColumns = function pickColumns(array2d, ...columns) {
210
216
  * @param {Number[]} [options.rows = null] - indices of the rows to pick
211
217
  * @param {Number[]} [options.columns = null] - indices of the columns to pick.
212
218
  * @returns {Array} - 2d array of only the rows and columns chosen.
213
- * @see {@link module:Array.pickRows} - picking rows
214
- * @see {@link module:Array.pickColumns} - picking columns
219
+ * @see {@link module:array.pickRows|array.pickRows} - picking rows
220
+ * @see {@link module:array.pickColumns|array.pickColumns} - picking columns
221
+ * @see {@link module:array.applyArrayValues|array.applyArrayValues} - applies a value / multiple values deeply into an array safely
215
222
  * @returns - 2dArray of the columns and rows requested
216
223
  * @example
217
224
  * data = [
@@ -250,6 +257,206 @@ module.exports.pick = function pick(array2d, options) {
250
257
  return results;
251
258
  };
252
259
 
260
+ /**
261
+ * Convenience function for picking specific rows and columns from a 2d array.
262
+ *
263
+ * Alias of {@link module:array.pick|array.pick}
264
+ *
265
+ * Please also see [Danfo.js](https://danfo.jsdata.org/) for working with DataFrames.
266
+ *
267
+ * @param {Array} array2d - 2d array to pick from [row][column]
268
+ * @param {Object} options - options on which to pick
269
+ * @param {Number[]} [options.rows = null] - indices of the rows to pick
270
+ * @param {Number[]} [options.columns = null] - indices of the columns to pick.
271
+ * @returns {Array} - 2d array of only the rows and columns chosen.
272
+ * @see {@link module:array.pickRows} - picking rows
273
+ * @see {@link module:array.pickColumns} - picking columns
274
+ * @returns - 2dArray of the columns and rows requested
275
+ * @example
276
+ * data = [
277
+ * ['john', 23, 'purple'],
278
+ * ['jane', 32, 'red'],
279
+ * ['ringo', 27, 'green']
280
+ * ];
281
+ *
282
+ * utils.array.pick(data, {rows: [0, 1]});
283
+ * //-- [['john', 23, 'purple'], ['jane', 32, 'red']];
284
+ *
285
+ * utils.array.pick(data, {columns: [0, 2]});
286
+ * //-- [['john', 'purple'], ['jane', 'red'], ['ringo', 'green']];
287
+ *
288
+ * utils.array.pick(data, {rows:[0, 1], columns:[0, 2]});
289
+ * //-- [['john', 'purple'], ['jane', 'red']];
290
+ */
291
+ module.exports.extract = module.exports.pick;
292
+
293
+ /**
294
+ * Applies deeply onto an array safely - in-place using dot-notation paths
295
+ * even if the child paths don't exist.
296
+ *
297
+ * While tthis can be as simple as safely applying a value even if targetObj may be null
298
+ *
299
+ * ```
300
+ * targetObj = [1, 2, null, 4, 5];
301
+ *
302
+ * utils.object.applyPropertyValue(targetObj, '[2]', 3);
303
+ * // [1, 2, 3, 4, 5]
304
+ * // equivalent to targetObj[2] = 3;
305
+ * ```
306
+ *
307
+ * This is much more safely working with deeply nested objects
308
+ *
309
+ * ```
310
+ * targetObj = [{
311
+ * name: 'john smith',
312
+ * class: {
313
+ * name: 'ECON_101',
314
+ * professor: {
315
+ * last_name: 'Winklemeyer'
316
+ * }
317
+ * }
318
+ * }];
319
+ *
320
+ * utils.object.applyPropertyValue(targetObj, '[0].class.professor.first_name', 'René');
321
+ * // [{
322
+ * // name: 'john smith',
323
+ * // class: {
324
+ * // name: 'ECON_101',
325
+ * // professor: {
326
+ * // last_name: 'Winklemeyer',
327
+ * // first_name: 'René' // <- Added
328
+ * // }
329
+ * // }
330
+ * // }];
331
+ * ```
332
+ *
333
+ * or creating intermediary objects along the path - if they did not exist first.
334
+ *
335
+ * ```
336
+ * targetObj = [{
337
+ * name: 'john smith'
338
+ * }];
339
+ * utils.object.applyPropertyValue(targetObj, '[0].class.professor.first_name', 'René');
340
+ * [{
341
+ * name: 'john smith',
342
+ * class: {
343
+ * professor: {
344
+ * first_name: 'René'
345
+ * }
346
+ * }
347
+ * }];
348
+ * ```
349
+ *
350
+ * @param {Array} collection - array to apply the value to
351
+ * @param {string} path - dot notation path to set the value, ex: 'geo', or 'states[0].prop'
352
+ * @param {any} value - value to set
353
+ * @returns {Array} - the base array
354
+ * @see {@link module:array.pick|array.pick} - to pick a row or column into an array
355
+ * @see {@link module:array.applyArrayValues|array.applyArrayValues} - applies an array safely and deeply onto another array of values
356
+ */
357
+ module.exports.applyArrayValue = function applyArrayValue(collection, path, value) {
358
+ // const signature = 'applyArrayValue(collection, path, value)';
359
+
360
+ if (!collection) return collection;
361
+ if (!path) return collection;
362
+
363
+ const cleanPath = String(path)
364
+ .replace(/\[/g, '.')
365
+ .replace(/\]/g, '.')
366
+ .replace(/[.]+/g, '.')
367
+ .replace(/^[.]+/, '')
368
+ .replace(/[.]$/, '');
369
+
370
+ const splitPath = cleanPath.split('.');
371
+ const terminalIndex = splitPath.length - 1;
372
+
373
+ return splitPath
374
+ .reduce((currentVal, prop, currentIndex) => {
375
+ //-- can no longer occur
376
+ // if (!prop) throw Error(`${signature}:Unable to set value with path:${path}`);
377
+
378
+ const isLeaf = currentIndex === terminalIndex;
379
+ if (isLeaf) {
380
+ // eslint-disable-next-line no-param-reassign
381
+ currentVal[prop] = value;
382
+ // if (value === undefined) {
383
+ // delete currentVal[prop];
384
+ // } else {
385
+ // currentVal[prop] = value;
386
+ // }
387
+ return collection;
388
+ }
389
+ //-- not a leaf
390
+ if (!currentVal[prop]) {
391
+ // eslint-disable-next-line no-param-reassign
392
+ currentVal[prop] = {};
393
+ }
394
+ return currentVal[prop];
395
+ }, collection);
396
+ };
397
+
398
+ /**
399
+ * Converse from the extractPropertyValue, this takes a value / set of values
400
+ * and applies the values for each index in the collection.
401
+ *
402
+ * for example:
403
+ *
404
+ * ```
405
+ * weather = [{ id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
406
+ * { id: 3, city: 'New York', month: 'Apr', precip: 3.94 },
407
+ * { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62 }];
408
+ *
409
+ * cities = utils.object.extractObjectProperty('city');
410
+ * // ['Seattle', 'New York', 'Chicago'];
411
+ *
412
+ * //-- async process to geocode
413
+ * geocodedCities = geocodeCity(cities);
414
+ * // [{ city: 'Seattle', state: 'WA', country: 'USA' },
415
+ * // { city: 'New York', state: 'NY', country: 'USA' },
416
+ * // { city: 'Chicago', state: 'IL', country: 'USA' }]
417
+ *
418
+ * utils.applyArrayValues(weather, 'geo', geocodedCities);
419
+ * // [{ id: 1, city: 'Seattle', month: 'Aug', precip: 0.87, geo: { city: 'Seattle', state: 'WA', country: 'USA' } },
420
+ * // { id: 3, city: 'New York', month: 'Apr', precip: 3.94, geo: { city: 'New York', state: 'NY', country: 'USA' } },
421
+ * // { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62, geo: { city: 'Chicago', state: 'IL', country: 'USA' } }];
422
+ *
423
+ * Note that traditional [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
424
+ * works best for if you are working with objects completely in memory.
425
+ *
426
+ * But this helps quite a bit if the action of mapping / transforming values
427
+ * needs to be separate from the extraction / application of values back.
428
+ *
429
+ * @param {Array} collection - array to apply the value to on each index
430
+ * @param {string} path - dot notation path to set the value within each index, ex: 'geo', or 'states[0].prop'
431
+ * @param {any} value - the value that should be set at that path.
432
+ * @returns {Object}
433
+ * @see {@link module:array.applyArrayValue|array.applyArrayValue} - to apply a single value to a single object
434
+ * @see {@link module:array.pick|array.pick} - to pick a row or column into an array
435
+ */
436
+ module.exports.applyArrayValues = function applyArrayValues(collection, path, valueList) {
437
+ // const signature = 'applyValue(objectList, path, valueList)';
438
+ if (!collection || !path) {
439
+ //-- do nothing
440
+ return collection;
441
+ }
442
+
443
+ const cleanCollection = Array.isArray(collection) ? collection : [collection];
444
+ const cleanValueList = Array.isArray(valueList) ? valueList : Array(cleanCollection.length).fill(valueList);
445
+
446
+ // if (cleanCollection.length !== cleanValueList) throw Error(
447
+ // `${signature}: objectList.length[${cleanCollection.length}] does not match valueList.length[${cleanValueList.length}]`
448
+ // );
449
+ const minLength = Math.min(cleanCollection.length, cleanValueList.length);
450
+
451
+ for (let i = 0; i < minLength; i += 1) {
452
+ const obj = cleanCollection[i];
453
+ const val = cleanValueList[i];
454
+ ArrayUtils.applyArrayValue(obj, path, val);
455
+ }
456
+
457
+ return collection;
458
+ };
459
+
253
460
  /**
254
461
  * Creates an array of a specific size and default value
255
462
  *
package/src/format.js CHANGED
@@ -41,6 +41,8 @@
41
41
  * * {@link module:format.parseNumber|format.parseNumber(val, locale)} - converts a value to a number
42
42
  * * Identifying values
43
43
  * * {@link module:format.isEmptyValue|format.isEmptyValue} - determine if a value is not 'empty'
44
+ * * Extracting values
45
+ * * {@link module:format.extractWords|format.extractWords} - to extract the words from a string
44
46
  *
45
47
  * @module format
46
48
  * @exports format
@@ -1286,3 +1288,56 @@ module.exports.replaceString = function replaceString(targetStr, stringTupletsOr
1286
1288
  }
1287
1289
  return FormatUtils.replaceStrings([targetStr], stringTupletsOrMap)[0];
1288
1290
  };
1291
+
1292
+ /**
1293
+ * Identify an array of words each from a string.
1294
+ *
1295
+ * Note that this uses the new [Unicode Properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Unicode_character_class_escape)
1296
+ * using `\p{L}` to identify characters based on unicode properties.
1297
+ *
1298
+ * For example:
1299
+ *
1300
+ * ```
1301
+ * strs = 'I am Modern "major-general".';
1302
+ * FormatUtils.extractWords(strs);
1303
+ * // ['I', 'am', 'Modern', 'major', 'general'];
1304
+ * ```
1305
+ *
1306
+ * you can also include additional characters that will no longer be considered word boundaries.
1307
+ *
1308
+ * ```
1309
+ * strs = 'I am Modern "major-general".';
1310
+ * FormatUtils.extractWords(strs);
1311
+ * // ['I', 'am', 'Modern', 'major-general'];
1312
+ * ```
1313
+ *
1314
+ * arrays of strings are also supported
1315
+ *
1316
+ * ```
1317
+ * strs = ['letras mayúsculas de tamaño igual a las minúsculas',
1318
+ * 'الاختراع، ومايكل هارت'];
1319
+ * FormatUtils.extractWords(strs);
1320
+ * // [ 'letras', 'mayúsculas', 'de', 'tamaño', 'igual', 'a', 'las', 'minúsculas', 'الاختراع', 'ومايكل', 'هارت'];
1321
+ * ```
1322
+ *
1323
+ * @param {String|String[]} strToExtractFrom - collection of strings to extract words from
1324
+ * @param {String} additionalNonBreakingCharacters - each char in this string will not be treated as a word boundry
1325
+ * @returns {string[]} - collection of words
1326
+ */
1327
+ module.exports.extractWords = function extractWords(strToExtractFrom, additionalNonBreakingCharacters) {
1328
+ if (!strToExtractFrom) return strToExtractFrom;
1329
+
1330
+ const cleanNonBreaking = additionalNonBreakingCharacters
1331
+ ? additionalNonBreakingCharacters
1332
+ : '';
1333
+
1334
+ const regex = cleanNonBreaking
1335
+ ? new RegExp(`[${additionalNonBreakingCharacters}\\p{L}]+`, 'gu')
1336
+ : /\p{L}+/ug; // -- old attempt/[A-Za-zÀ-ÖØ-öø-ÿ]+/g;
1337
+
1338
+ const cleanStrings = Array.isArray(strToExtractFrom)
1339
+ ? strToExtractFrom
1340
+ : [strToExtractFrom];
1341
+
1342
+ return cleanStrings.reduce((result, str) => [...result, ...((str || '').match(regex) || [])], []);
1343
+ };
package/src/object.js CHANGED
@@ -13,29 +13,33 @@ const FormatUtils = require('./format');
13
13
  * * {@link module:object.generateSchema|generateSchema()} - generate a schema / describe properties of a list of objects
14
14
  * * {@link module:object.findWithoutProperties|findWithoutProperties()} - find objects without ALL the properties specified
15
15
  * * {@link module:object.findWithoutProperties|findWithProperties()} - find objects with any of the properties specified
16
- * * {@link module:object.setPropertyDefaults|setPropertyDefaults()} - sets values for objects that don't currently have the property
17
16
  * * {@link module:object.propertyValueSample|propertyValueSample(collection)} - finds non-empty values for all properties found in the collection
18
- * * Manipulating objects
19
- * * {@link module:object.assign|objAssign(object, property, value)} - Applies properties to an object in functional programming style.
20
- * * {@link module:object.assignEntities|objAssignEntities(object, [property, value])} - Applies properties to an object using Array values - [key,value]
21
- * * {@link module:object.augment|augment(object, augmentFn)} - Applies properties to an object similar to Map
22
- * * {@link module:object.augmentInherit|augmentInherit(object, augmentFn)} - Applies properties to a collection of objects, 'remembering' the last value - useful for 1d to *D lists.
23
- * * {@link module:object.selectObjectProperties|selectObjectProperties()} - keep only specific properties
24
- * * {@link module:object.filterObjectProperties|filterObjectProperties()} - remove specific properties
25
- * * {@link module:object.mapProperties|mapProperties(collection, fn, ...properties)} - map multiple properties at once (like parseInt, or toString)
26
- * * {@link module:object.formatProperties|formatProperties(collection, propertyTranslation)} - map specific properties (ex: toString, toNumber, etc)
27
- * * {@link module:object.union|union(objectList1, objectList2)} - Unites the properties of two collections of objects.
28
17
  * * Fetch child properties from related objects
29
18
  * * {@link module:object.fetchObjectProperty|fetchObjectProperty(object, string)} - use dot notation to bring a child property onto a parent
30
19
  * * {@link module:object.fetchObjectProperties|fetchObjectProperties(object, string[])} - use dot notation to bring multiple child properties onto a parent
31
20
  * * {@link module:object.join|join(array, index, map, fn)} - join a collection against a map by a given index
32
21
  * * {@link module:object.joinProperties|join(array, index, map, ...fields)} - join a collection, and copy properties over from the mapped object.
22
+ * * Fetch values safely
33
23
  * * {@link module:object.propertyFromList|propertyFromList(array, propertyName)} - fetches a specific property from all objects in a list
34
24
  * * {@link module:object.extractObjectProperty|extractObjectProperty(list, propertyNameOrFn)} - extracts a property or fn across all objects in list.
35
25
  * * {@link module:object.extractObjectProperties|extractObjectProperties(list, propertyNameOrFnMap)} - extracts multiple propertie or fn across all objects in list.
26
+ * * Apply deep values safely
27
+ * * {@link module:object.assign|objAssign(object, property, value)} - Applies properties to an object in functional programming style.
28
+ * * {@link module:object.augment|augment(object, augmentFn)} - Applies properties to an object similar to Map
29
+ * * {@link module:object.assignEntities|objAssignEntities(object, [property, value])} - Applies properties to an object using Array values - [key,value]
30
+ * * {@link module:object.setPropertyDefaults|setPropertyDefaults()} - sets values for objects that don't currently have the property
31
+ * * {@link module:object.applyPropertyValue|object.applyPropertyValue} - safely apply a value deeply and safely
32
+ * * {@link module:object.applyPropertyValues|object.applyPropertyValues} - apply an array of values safely and deeply against a list of objects.
33
+ * * Manipulating objects
34
+ * * {@link module:object.augmentInherit|augmentInherit(object, augmentFn)} - Applies properties to a collection of objects, 'remembering' the last value - useful for 1d to *D lists.
35
+ * * {@link module:object.selectObjectProperties|selectObjectProperties()} - keep only specific properties
36
+ * * {@link module:object.filterObjectProperties|filterObjectProperties()} - remove specific properties
37
+ * * {@link module:object.mapProperties|mapProperties(collection, fn, ...properties)} - map multiple properties at once (like parseInt, or toString)
38
+ * * {@link module:object.formatProperties|formatProperties(collection, propertyTranslation)} - map specific properties (ex: toString, toNumber, etc)
39
+ * * {@link module:object.union|union(objectList1, objectList2)} - Unites the properties of two collections of objects.
36
40
  * * Rename properties
37
41
  * * {@link module:object.cleanProperties|cleanProperties()} - correct inaccessible property names in a list of objects - in place
38
- * * * {@link module:object.cleanProperties2|cleanProperties2()} - correct inaccessible property names in a list of objects - on a cloned list
42
+ * * {@link module:object.cleanProperties2|cleanProperties2()} - correct inaccessible property names in a list of objects - on a cloned list
39
43
  * * {@link module:object.cleanPropertyNames|cleanPropertyNames()} - create a translation of inaccessible names to accessible ones
40
44
  * * {@link module:object.cleanPropertyName|cleanPropertyName()} - create a translation of a specific property name to be accessible.
41
45
  * * {@link module:object.renameProperties|renameProperties()} - Use a translation from old property names to new ones
@@ -518,7 +522,9 @@ module.exports.filterObjectProperties = function filterObjectProperties(list, pr
518
522
  * @param {Function | String} propertyOrFn - Name of the property or accessor function
519
523
  * @returns {Array} - single array the values stored in propertyOrFn across all objects in objectList.
520
524
  * @see {@link module:aggregate.unique|unique()} to see all the unique values stored
521
- * @see {@link module:object.extractObjectProperties|extractObjectProperties(list, propertyNameOrFnMap)} to see the values extracted into a single object / horizontal transpose.
525
+ * @see {@link module:object.extractObjectProperties|object.extractObjectProperties} - to extract into array vectors
526
+ * @see {@link module:object.fetchObjectProperty|object.fetchObjectProperty} - to extract a deep value and optionally throw if not found
527
+ * @see {@link module:object.applyPropertyValue|object.applyPropertyValue} - to apply a single value to a single object using dot notation safely
522
528
  */
523
529
  module.exports.extractObjectProperty = function extractObjectProperty(list, propertyOrFn) {
524
530
  let cleanList = !list ? [] : Array.isArray(list) ? list : [list];
@@ -560,6 +566,8 @@ module.exports.extractObjectProperty = function extractObjectProperty(list, prop
560
566
  * @param {Map<Function | String>} propertyOrFnMap - Name of the property or accessor function
561
567
  * @returns {Object} - Object with the keys in the map as properties - extracting the values across all in list.
562
568
  * @see {@link module:object.extractObjectProperty|extractObjectProperty(list, propertyNameOrFn)} to see all the values stored for a single property.
569
+ * @see {@link module:object.applyPropertyValues|object.applyPropertyValues} - to safely and deeply apply the list of values extracted to a list of objects.
570
+ * @see {@link module:object.fetchObjectProperties} - to fetch multiple properties at once into objects
563
571
  */
564
572
  module.exports.extractObjectProperties = function extractObjectProperties(list, propertyOrFnMap) {
565
573
  let propertyEntries = [];
@@ -585,8 +593,6 @@ module.exports.extractObjectProperties = function extractObjectProperties(list,
585
593
  const results = {};
586
594
  propertyEntries.forEach(([propertyName, propertyOrFn]) => {
587
595
  results[propertyName] = ObjectUtils.extractObjectProperty(list, propertyOrFn || propertyName);
588
- // const fn = ObjectUtils.evaluateFunctionOrProperty(propertyOrFn);
589
- // results[propertyName] = cleanList.map(fn);
590
596
  });
591
597
 
592
598
  return results;
@@ -600,13 +606,27 @@ module.exports.extractObjectProperties = function extractObjectProperties(list,
600
606
 
601
607
  /**
602
608
  * Fetches multiple properties from an object or list of objects.
609
+ *
610
+ * ```
611
+ * testObj = {
612
+ * name: 'john',
613
+ * courses: [{ name: 'econ-101' }]
614
+ * }
615
+ * utils.object.fetchObjectProperty(testObj,
616
+ * { 'courseName': 'courses[0].?name', personName: 'name' });
617
+ * // { courseName: 'econ-101', personName: 'john' }
618
+ * ```
619
+ *
603
620
  * @param {Object | Object[]} list - collection of objects to reduce
604
- * @param {Map<String,any>} propertyNames - Object with the keys as the properties
621
+ * @param {Object<String,any>} propertyNames - Object with the keys as as properties to return,
605
622
  * and the values using dot notation to access related records and properties
606
623
  * (ex: {parentName: 'somePropertyObject.parent.parent.name', childName: 'child.Name'})
607
624
  * @param {FetchObjectOptions} options - {@link module:object~FetchObjectOptions|See FetchObjectOptions}
608
625
  * @returns {Object[]} - objects with the properties resolved
609
626
  * (ex: {parentname, childName, etc.})
627
+ * @see {@link module:object.fetchObjectProperty|object.fetchObjectProperty} - to safely fetch a single value
628
+ * @see {@link module:object.applyPropertyValues|object.applyPropertyValues} - to safely and deeply apply the list of values extracted to a list of objects.
629
+ * @see {@link module:object.extractObjectProperties|object.extractObjectProperties} - to extract into array vectors instead of objects
610
630
  */
611
631
  module.exports.fetchObjectProperties = function fetchObjectProperties(list, propertyNames, options = {}) {
612
632
  if (!list) return [];
@@ -630,11 +650,41 @@ module.exports.fetchObjectProperties = function fetchObjectProperties(list, prop
630
650
 
631
651
  /**
632
652
  * Accesses a property using a string
653
+ *
654
+ * ```
655
+ * testObj = {
656
+ * name: 'john',
657
+ * courses: [{ name: 'econ-101' }]
658
+ * }
659
+ * utils.object.fetchObjectProperty(testObj, 'courses[0].?name');
660
+ * // 'econ-101'
661
+ * ```
662
+ *
663
+ * note that the options allow for safe property access
664
+ *
665
+ * ```
666
+ * testObj = {
667
+ * name: 'john',
668
+ * courses: [{ name: 'econ-101' }]
669
+ * }
670
+ * utils.object.fetchObjectProperty(testObj, 'courses[0].courseId');
671
+ * // throws an error
672
+ *
673
+ * utils.object.fetchObjectProperty(testObj, 'courses[0].?courseId');
674
+ * // null - because of optional condition operator
675
+ *
676
+ * utils.object.fetchObjectProperty(testObj, 'courses[0].courseId', { safeAccess: true });
677
+ * // null - because of the safe access option
678
+ * ```
679
+ *
633
680
  * @param {Object} obj - object to access the properties on
634
681
  * @param {String} propertyAccess - dot notation for the property to access
635
682
  * (ex: `parent.obj.Name`)
636
683
  * @param {FetchObjectOptions} options - {@link module:object~FetchObjectOptions|See FetchObjectOptions}
637
684
  * @returns {any} - the value accessed at the end ofthe property chain
685
+ * @see {@link module:object.fetchObjectProperties} - to fetch multiple properties at once into objects
686
+ * @see {@link module:object.extractObjectProperty|object.extractObjectProperty} - to safely extract a deep value without options
687
+ * @see {@link module:object.applyPropertyValue|object.applyPropertyValue} - to apply a single value to a single object using dot notation safely
638
688
  */
639
689
  module.exports.fetchObjectProperty = function fetchObjectProperty(obj, propertyAccess, options = {}) {
640
690
  if (!obj || !propertyAccess) return null;
@@ -643,8 +693,14 @@ module.exports.fetchObjectProperty = function fetchObjectProperty(obj, propertyA
643
693
  safeAccess = false
644
694
  } = options;
645
695
 
646
- //-- @TODO - should we be safe or support elvis operators?
647
- return propertyAccess.split('.')
696
+ const cleanPropertyAccess = String(propertyAccess)
697
+ .replace(/\[/g, '.')
698
+ .replace(/\]/g, '.')
699
+ .replace(/[.]+/g, '.')
700
+ .replace(/^[.]+/, '')
701
+ .replace(/[.]$/, '');
702
+
703
+ return cleanPropertyAccess.split('.')
648
704
  .reduce((currentVal, prop) => {
649
705
  let isElvis = false;
650
706
  let cleanProp = prop;
@@ -661,6 +717,142 @@ module.exports.fetchObjectProperty = function fetchObjectProperty(obj, propertyA
661
717
  }, obj);
662
718
  };
663
719
 
720
+ /**
721
+ * Applies a target value onto a source object in-place safely - using dot-notation paths.
722
+ *
723
+ * This can be as simple as safely applying a value even if targetObj may be null
724
+ * ```
725
+ * targetObj = { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 };
726
+ * utils.object.applyPropertyValue(targetObj, 'state', 'WA');
727
+ * // { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87, state: 'WA };
728
+ * ```
729
+ *
730
+ * working with deeply nested objects
731
+ * ```
732
+ * targetObj = { name: 'john smith', class: { name: 'ECON_101', professor: { last_name: 'Winklemeyer' }} };
733
+ * utils.object.applyPropertyValue(targetObj, 'class.professor.first_name', 'René');
734
+ * // { name: 'john smith', class: { name: 'ECON_101', professor: { last_name: 'Winklemeyer', first_name: 'René' }} };
735
+ * ```
736
+ *
737
+ * or safely working with arrays of values
738
+ * ```
739
+ * targetObj = { name: 'john smith', classes: [{ name: 'ECON_101' }] };
740
+ * utils.object.applyPropertyValue(targetObj, 'classes[0].grade', 'A');
741
+ * // { name: 'john smith', classes: [{ name: 'ECON_101', grade: 'A' }] };
742
+ * ```
743
+ *
744
+ * @param {Object} obj - object to apply the value to
745
+ * @param {string} path - dot notation path to set the value, ex: 'geo', or 'states[0].prop'
746
+ * @param {any} value - value to set
747
+ * @returns {Object} - the object the value was applied to
748
+ * @see {@link module:object.applyPropertyValues|object.applyPropertyValues} - to safely and deeply apply the list of values extracted to a list of objects.
749
+ * @see {@link module:object.extractObjectProperty|object.extractObjectProperty} - to safely extract a deep value
750
+ * @see {@link module:object.fetchObjectProperty|object.fetchObjectProperty} - to extract a deep value and optionally throw if not found
751
+ */
752
+ module.exports.applyPropertyValue = function applyPropertyValue(obj, path, value) {
753
+ // const signature = 'applyPropertyValue(obj, path, value)';
754
+
755
+ if (!obj) return obj;
756
+ if (!path) return obj;
757
+
758
+ const cleanPath = String(path)
759
+ .replace(/\[/g, '.')
760
+ .replace(/\]/g, '.')
761
+ .replace(/[.]+/g, '.')
762
+ .replace(/^[.]+/, '')
763
+ .replace(/[.]$/, '');
764
+
765
+ const splitPath = cleanPath.split('.');
766
+ const terminalIndex = splitPath.length - 1;
767
+
768
+ return splitPath
769
+ .reduce((currentVal, prop, currentIndex) => {
770
+ //-- can no longer occur
771
+ // if (!prop) throw Error(`${signature}:Unable to set value with path:${path}`);
772
+
773
+ const isLeaf = currentIndex === terminalIndex;
774
+ if (isLeaf) {
775
+ // eslint-disable-next-line no-param-reassign
776
+ currentVal[prop] = value;
777
+ // if (value === undefined) {
778
+ // delete currentVal[prop];
779
+ // } else {
780
+ // currentVal[prop] = value;
781
+ // }
782
+ return obj;
783
+ }
784
+ //-- not a leaf
785
+ if (!currentVal[prop]) {
786
+ // eslint-disable-next-line no-param-reassign
787
+ currentVal[prop] = {};
788
+ }
789
+ return currentVal[prop];
790
+ }, obj);
791
+ };
792
+
793
+ /**
794
+ * Opposite of the extractObjectProperty, this takes a value / set of values
795
+ * and applies them along a given path on each of the target objects.
796
+ *
797
+ * for example:
798
+ *
799
+ * ```
800
+ * weather = [{ id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
801
+ * { id: 3, city: 'New York', month: 'Apr', precip: 3.94 },
802
+ * { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62 }];
803
+ *
804
+ * cities = utils.object.extractObjectProperty('city');
805
+ * // ['Seattle', 'New York', 'Chicago'];
806
+ *
807
+ * //-- async process to geocode
808
+ * geocodedCities = geocodeCity(cities);
809
+ * // [{ city: 'Seattle', state: 'WA', country: 'USA' },
810
+ * // { city: 'New York', state: 'NY', country: 'USA' },
811
+ * // { city: 'Chicago', state: 'IL', country: 'USA' }]
812
+ *
813
+ * utils.applyPropertyValues(weather, 'geo', geocodedCities);
814
+ * [{ id: 1, city: 'Seattle', month: 'Aug', precip: 0.87, geo: { city: 'Seattle', state: 'WA', country: 'USA' } },
815
+ * { id: 3, city: 'New York', month: 'Apr', precip: 3.94, geo: { city: 'New York', state: 'NY', country: 'USA' } },
816
+ * { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62, geo: { city: 'Chicago', state: 'IL', country: 'USA' } }];
817
+ *
818
+ * Note that traditional [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
819
+ * works best for if you are working with objects completely in memory.
820
+ *
821
+ * But this helps quite a bit if the action of mapping / transforming values
822
+ * needs to be separate from the extraction / application of values back.
823
+ *
824
+ * @param {Object} obj - object to apply the value to
825
+ * @param {string} path - dot notation path to set the value, ex: 'geo', or 'states[0].prop'
826
+ * @param {any} value - the value that should be set at that path.
827
+ * @returns {Object}
828
+ * @see {@link module:object.applyPropertyValue} - to apply a single value to a single object
829
+ * @see {@link module:object.fetchObjectProperties} - to fetch multiple properties at once into objects
830
+ * @see {@link module:object.extractObjectProperties|object.extractObjectProperties} - to extract properties into array vectors instead of objects
831
+ */
832
+ module.exports.applyPropertyValues = function applyPropertyValues(objectList, path, valueList) {
833
+ // const signature = 'applyPropertyValues(objectList, path, valueList)';
834
+ if (!objectList || !path) {
835
+ //-- do nothing
836
+ return objectList;
837
+ }
838
+
839
+ const cleanObjectList = Array.isArray(objectList) ? objectList : [objectList];
840
+ const cleanValueList = Array.isArray(valueList) ? valueList : Array(cleanObjectList.length).fill(valueList);
841
+
842
+ // if (cleanObjectList.length !== cleanValueList) throw Error(
843
+ // `${signature}: objectList.length[${cleanObjectList.length}] does not match valueList.length[${cleanValueList.length}]`
844
+ // );
845
+ const minLength = Math.min(cleanObjectList.length, cleanValueList.length);
846
+
847
+ for (let i = 0; i < minLength; i += 1) {
848
+ const obj = cleanObjectList[i];
849
+ const val = cleanValueList[i];
850
+ ObjectUtils.applyPropertyValue(obj, path, val);
851
+ }
852
+
853
+ return objectList;
854
+ };
855
+
664
856
  /**
665
857
  * Translates specific properties to a new value on an object, or collection of objects.
666
858
  *