UNPKG

bubble-gum-tools

Version:

Work with nested objects is easy with a bubble-gum

545 lines (513 loc) 13.6 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); 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); } /** * */ /** * It creates a new object or an initialized array depending on the input path * * @alias module:bubble-gum-tools.create * @example * * ```js * const create = require('bubble-gum-tools').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); } /** * 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 {*} */ /** * */ /** * * 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-tools.goto * @example * * ```javascript * * const goto = require('bubble-gum-tools').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, }); }; } /** * */ /** * It gets a property from a nested object or a nested array using an array path * * @alias module:bubble-gum-tools.get * @example * * ```javascript * * const get = require('bubble-gum-tools').get; * * const target = { * root: { * foo: 'bar', * }, * arr: [[ * ['baz'], * ]], * }; * * // Working with nested objects * const bar = get(target, ['root', 'foo']); * console.log(bar); // => 'bar' * * // Working with nested arrays * const baz = get(target, ['arr', 0, 0, 0]); * console.log(baz); // => 'baz' * * // Set a default * const defaultVal = get(target, ['no', 'defined'], 'default'); * console.log(defaultVal); // => 'default' * * ``` * * @param {Object|Array} target - Target object or target array * @param {Array} path - Path to property * @param {*} [defaultValue] - Value to be returned in case the property does not exist * @return {*} propertyValue */ function get(target, path, defaultValue) { (undefined == target) && function(err) { throw err; }(new TypeError('target shoulds be a valid value')); return goto(path, function _get(ref) { var current = ref.current; return (undefined === current) ? defaultValue : current; })(target); } var _hasFnNoStrict = function hasNoStrict(ref) { var current = ref.current; return (!!current); }; var _hasFnStrict = function hasNoStrict(ref) { var current = ref.current; return !(current === false || undefined == current); }; /** * */ /** * It checks if the property exists in a nested object or a nested array using an array path * @alias module:bubble-gum-tools.has * @example * * * ```javascript * * const has = require('bubble-gum-tools').has; * * const target1 = { * root: { * foo: 'bar', * }, * arr: [[ * ['baz'], * ]], * }; * * const existsBar = has(target1, ['root', 'foo']); * console.log(existsBar); // => true * * const existsBaz = has(target1, ['arr', 0, 0, 0]); * console.log(existsBaz); // => true * * const noExists = has(target1, ['no', 'defined']); * console.log(noExists); // => false * * ``` * * **isStrict = false** * * ```javascript * * const has = require('bubble-gum-tools').has; * * const target = { * root: { * zero: 0, * null: null, * empty: '', * false: false, * }, * }; * * const isNotStrict = false; * const noStrictZero = has(target, ['root', 'zero'], isNotStrict); * console.log(noStrictZero); // => false * const noStrictNull = has(target, ['root', 'null'], isNotStrict); * console.log(noStrictNull); // => false * const noStrictEmpty = has(target, ['root', 'empty'], isNotStrict); * console.log(noStrictEmpty); // => false * const noStrictFalse = has(target, ['root', 'false'], isNotStrict); * console.log(noStrictFalse); // => false * * ``` * * **isStrict = true** * * ```javascript * * * const has = require('bubble-gum-tools').has; * * const target = { * root: { * zero: 0, * null: null, * empty: '', * false: false, * }, * }; * * const isStrict = true; * const strictZero = has(target, ['root', 'zero'], isStrict); * console.log(strictZero); // => true * const strictEmpty = has(target, ['root', 'empty'], isStrict); * console.log(strictEmpty); // => true * const strictNull = has(target, ['root', 'null'], isStrict); * console.log(strictNull); // => false * const strictFalse = has(target, ['root', 'false'], isStrict); * console.log(strictFalse); // => false * * ``` * * @param {Object|Array} target - Target object or target array * @param {Array} path - Path to property * @param {Boolean} [isStrict=false] - is strict * @return {Boolean} exists - Returns if the property exists */ function has(target, path, isStrict) { if ( isStrict === void 0 ) isStrict = false; (undefined == target) && function(err) { throw err; }(new TypeError('target shoulds be a valid value')); var hasFn = isStrict ? _hasFnStrict : _hasFnNoStrict; return goto(path, hasFn)(target); } 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'; } } /** * */ /** * 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-tools.set * @example * * ```javascript * * const set = require('bubble-gum-tools').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); } /** * */ /** * It slices a object or an array generating a new object or a new array * * @alias module:bubble-gum-tools.slice * @example * * ```javascript * * const slice = require('bubble-gum-tools').slice; * * const target = { * root: { foo: 'bar' }, * arr: [[['baz']]], * }; * * const sliced1 = slice(target, [{ * path: ['root', 'foo'] * }]); * console.log(sliced1); // => { root: { foo: 'bar' } } * * const sliced2 = slice(target, [{ * path: ['root', 'foo'], * newPath: ['bar'], * }]); * console.log(sliced2); // => { bar: 'bar' } * * const sliced3 = slice(target, [{ * path: ['root', 'foo'], * newPath: [0], * }]); * console.log(sliced3); // => { '0': 'bar' } * * const sliced4 = slice(target, [{ * path: ['arr', 0, 0, 0], * newPath: ['baz'], * }, { * path: ['root', 'foo'], * newPath: ['bar'], * }]); * console.log(sliced4); // => { baz: 'baz', bar: 'bar' } * * ``` * * @param {Object|Array} target - Target object or target array * @param {Array.Object} config - Array with the configuration of the slice * @param {Array} config[].path - Path to the property to be sliced * @param {Array} config[].newPath - Path to sets a new value in the slicedObject, if this is not defined, this will have the same value as the config[].path * @return {Object} slicedObject - New object or new array with sliced values */ function slice(target, config) { return config.reduce(function (slicedObject, ref) { var path = ref.path; var newPath = ref.newPath; var property = get(target, path); if (property === undefined) { return slicedObject; } set(slicedObject, (newPath || path), property); return slicedObject; }, {}); } /** * Tools for work with nested objects * * @author Nicolas Quiceno <nquicenob@gmail.com> * @link https://github.com/nquicenob/bubble-gum-tools * */ /** * @module bubble-gum-tools * * @example * * * ```javascript * const bubbleGumTools = require('bubble-gum-tools'); * * const nestedObj = { * root: [{ * foo: 'bar', * }], * }; * * // get * const foo = bubbleGumTools.get(nestedObj, ['root', 0, 'foo']); * console.log(foo); // => 'bar' * * // has * const existsFoo = bubbleGumTools.has(nestedObj, ['root', 0, 'foo']); * console.log(existsFoo); // => true * * // set * bubbleGumTools.set(nestedObj, ['root', 0, 'foo'], 'newBar'); * console.log(nestedObj); // => { root: [{ foo: 'newBar' }] } * * // slice * const sObject = bubbleGumTools.slice(nestedObj, [{ * path: ['root', 0, 'foo'], * newPath: ['newFoo'], * }]); * console.log(sObject); // => { newFoo: 'newBar' } * * // create * const cObject = bubbleGumTools.create(['other-root', 0, 'other-foo'], 'other-bar'); * console.log(cObject); // => { 'other-root': [{ 'other-foo': 'other-bar' }] } * * // goto * const resultGOTO = bubbleGumTools.goto(['other-root', 0, 'other-foo'], ({ current, key }) => ({ * [key]: current, * }))(cObject); * console.log(resultGOTO); // => { 'other-foo': 'other-bar' } * * ``` */ var bubblegumTools ={ create: create, get: get, goto: goto, has: has, set: set, slice: slice, }; exports['default'] = bubblegumTools; exports.create = create; exports.get = get; exports.goto = goto; exports.has = has; exports.set = set; exports.slice = slice;