UNPKG

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

235 lines (221 loc) 6.18 kB
/** * 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); } export default set;