bubble-gum-tools
Version:
Work with nested objects is easy with a bubble-gum
545 lines (513 loc) • 13.6 kB
JavaScript
'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;