src/Utils/HierarchicalCollection.js
- const assert = require('assert');
- const TypeCheck = require('js-typecheck');
- const deepMerge = require('./deepMerge');
-
- // symbols used for private members to avoid any potential clashing
- // caused by re-implementations
- const _collection = Symbol('collection');
-
-
- /**
- * Provides an interface to deal with data in a multi dimension plain object.
- *
- * It automatically creates the intermediate levels when assigning a value under
- * nested levels to the collection. Also, if the value already exists
- * under the collection then it allows the value to be merged with the
- * existing values.
- * @private
- */
- class HierarchicalCollection{
-
- /**
- * Creates a new instance
- */
- constructor(){
- this[_collection] = {};
- }
-
- /**
- * Inserts a new value to the collection
- *
- * @param {string} path - path about where the value should be stored (the levels
- * must be separated by '.').
- * @param {*} value - value that is going to be stored under the collection
- * @param {boolean} [merge=true] - this option is used to decide in case of the
- * last level is already existing under the collection, if the value should be
- * either merged (default) or overridden.
- */
- insert(path, value, merge=true){
- assert(TypeCheck.isString(path), 'path needs to be defined as string');
- assert(path.length, 'path cannot be empty');
- assert((/^([\w_\.\-])+$/gi).test(path), `Illegal path name: ${path}`); // eslint-disable-line no-useless-escape
-
- let currentLevel = this[_collection];
- let finalLevel = path;
-
- // building the intermediate levels if necessary
- if (path.indexOf('.') !== -1){
- const levels = path.split('.');
- for (const level of levels.slice(0, -1)){
- if (!(level in currentLevel)){
- currentLevel[level] = {};
- }
-
- currentLevel = currentLevel[level];
- }
- finalLevel = levels.slice(-1)[0];
- }
-
- // assigning value
- if (merge && TypeCheck.isPlainObject(value) && finalLevel in currentLevel){
- const merged = deepMerge(currentLevel[finalLevel], value);
- Object.assign(currentLevel[finalLevel], merged);
- }
- else{
- currentLevel[finalLevel] = value;
- }
- }
-
- /**
- * Returns a value under the collection
- *
- * @param {string} path - path about where the value is localized (the levels
- * must be separated by '.'). In case of empty string the entire collection
- * is returned.
- * @param {*} [defaultValue] - default value returned in case a value was
- * not found for the path
- * @return {*}
- */
- query(path, defaultValue=undefined){
- assert(TypeCheck.isString(path), 'path needs to be defined as string');
-
- let currentLevel = this[_collection];
-
- // returning the entire collection
- if (!path.length){
- return currentLevel;
- }
-
- // no intermediate levels
- if (path.indexOf('.') === -1){
- if (!(path in currentLevel)){
- return defaultValue;
- }
- return currentLevel[path];
- }
-
- // otherwise find the value going through the intermediate levels
- const levels = path.split('.');
- for (const level of levels){
- if (!(level in currentLevel)){
- return defaultValue;
- }
-
- currentLevel = currentLevel[level];
- }
-
- return currentLevel;
- }
-
- /**
- * Returns a list of the root levels
- *
- * @return {Array<string>}
- */
- root(){
- return Object.keys(this[_collection]);
- }
- }
-
- module.exports = HierarchicalCollection;