bubble-gum-set
Version:
Sets a new value in a nested object or a nested array using an array path, if the path does not exist create this
237 lines (222 loc) • 6.2 kB
JavaScript
;
/**
* Callback at the end of the loop
*
* @callback actionCallback
* @param {Object} options - Values in the end of the path
* @param {Number} options.indexPath - Current index in the array path
* @param {Object|Array} options.target - Target object or target array
* @param {*} [options.current] - Current value in target object
* @param {*} [options.key] - Current value in the path
* @param {*} [options.previous] - Previous value in target object
* @return {*}
*/
/**
* @module bubble-gum-goto
*/
/**
*
* It receives a input path and a callback(actionCallback), It returns the function **_goto**,
* the *_goto* function receives a target object or target array,
* when the *_goto* is called, this navigates the target object or target array using the input path,
* when it reaches the end of the path, *_goto* executs the callback and returns the result
*
* @alias module:bubble-gum-goto
* @example
*
* ```javascript
*
* const goto = require('bubble-gum-goto');
*
* const target = {
* root: {
* foo: 'bar',
* },
* };
*
* goto(['root', 'foo'], (result) => {
* const {
* indexPath,
* previous,
* target,
* current,
* key,
* } = result;
* console.log(indexPath); // => 1
* console.log(previous); // => { foo: 'bar' }
* console.log(target); // => { root: { foo: 'bar' }, arr: [ [ [Object] ] ] }
* console.log(current); // => bar
* console.log(key); // => foo
* })(target);
*
* const result = goto(['root', 'foo'], ({current, key}) => (current + '-' + key))(target);
* console.log(result); // => bar-foo
*
* ```
*
* @param {Array} path - Path to property
* @param {actionCallback} fn - Callback with the action that will be called at the end of the path
* @return {Function}
*/
function goto(path, fn) {
var len = path.length;
return function _goto(target) {
var previousValue, currentPath, indexPath;
var currentValue = target;
var init = 0;
while((undefined !== currentValue && null !== currentValue) && (init < len)) {
if (init > 0) {
previousValue = currentValue;
}
currentPath = path[init];
currentValue = currentValue[currentPath];
indexPath = init;
init++;
}
return fn({
current: currentValue,
key: currentPath,
indexPath: indexPath,
previous: previousValue,
target: target,
});
};
}
function build(index, value) {
if (Number.isSafeInteger(index) && index >= 0) {
var arr = [];
arr[index] = value;
return arr;
}
return ( obj = {}, obj[index] = value, obj );
var obj;
}
function getInitValue(path, initValue, last) {
return (path.length === 0 || initValue === undefined) ?
{} : build(last, initValue);
}
/**
* @module bubble-gum-create
*/
/**
* It creates a new object or an initialized array depending on the input path
*
* @alias module:bubble-gum-create
* @example
*
* ```js
* const create = require('bubble-gum-create');
*
* // create nested arrays
* const nestedArray = create([0, 2, 0], 'bar');
* console.log(nestedArray); // => [ [ , , [ 'bar' ] ] ]
*
* // create nested objects
* const nestedObject = create(['root', 'foo', 'bar'], 'bar');
* console.log(nestedObject); // => { root: { foo: { bar: 'bar' } } }
*
* // no defined value
* const noDefaultVal = get(target, ['no', 'defined']);
* console.log(noDefaultVal); // => undefined
*
* // create both
* const mixed = create([0, 'nested', 'key'], 'value');
* console.log(mixed); // => [ { nested: { key: 'value' } } ]
*
* ```
*
* @param {Array} path - Input path with the structure
* @param {*} initValue - Initial value for the end of the input path structure
* @return {Object|Array} output - The new array or new object with the input path structure
*
*/
function create(path, initValue) {
(!Array.isArray(path)) && function(err) {
throw err;
}(new TypeError('path shoulds be an Array'));
var _path = [].concat(path);
var last = _path.pop();
var init = getInitValue(path, initValue, last);
return _path.reduceRight(function (prev, keyPath) { return build(keyPath, prev); }, init);
}
function isObject(value) {
return Object(value) === value;
}
function getType(value) {
if (isObject(value)) {
return 'OBJECT';
} else if (Array.isArray(value)) {
return 'ARRAY';
} else if (undefined === value) {
return 'UNDEFINED';
} else {
return 'OTHERS';
}
}
/**
* @module bubble-gum-set
*/
/**
* It sets a new value in a nested object or a nested array using an array path, if the path does not exist create this
*
* @alias module:bubble-gum-set
* @example
*
* ```javascript
*
* const set = require('bubble-gum-set');
*
* const target = {
* root: {
* foo: 'bar',
* },
* arr: [[
* ['baz'],
* ]],
* };
*
* set(target, ['root', 'foo'], 'newbar');
* console.log(target.root.foo); // => 'newbar'
*
* set(target, ['arr', 0, 0, 0], 'newbaz');
* console.log(target.arr[0][0][0]); // => 'newbaz'
*
* set(target, ['root', 'foo2'], 'foo2');
* console.log(target.root.foo2); // => 'foo2'
*
* set(target, ['arr', 0, 0, 1], 'newbaz2');
* console.log(target.arr[0][0][1]); // => 'newbaz2'
*
* ```
*
*
* @param {Object|Array} target - Target object or target array
* @param {Array} path - Path to property
* @param {*} valueToSet - Value to set in target
*/
function set(target, path, valueToSet) {
(undefined == target || undefined == path) && function(err) {
throw err;
}(new TypeError('shoulds be a valid value'));
var _path = [].concat(path);
var last = _path.pop();
goto(_path, function _set(ref) {
var current = ref.current;
var key = ref.key;
var indexPath = ref.indexPath;
var previous = ref.previous; if ( previous === void 0 ) previous = target;
switch (getType(current)) {
case 'OBJECT':
case 'ARRAY':
current[last] = valueToSet;
break;
case 'UNDEFINED':
Object.assign(previous, create(path.slice(indexPath), valueToSet));
break;
default:
previous[key] = create([last], valueToSet);
break;
}
})(target);
}
module.exports = set;