# Source: businessRules/rules/arrayParsing/dataArrayParsing.js

    /**
     * @file dataArrayParsing.js
     * @module dataArrayParsing
     * @description Contains all system defined business rules for parsing arrays specific to data.
     * @requires module:dataBroker
     * @requires module:ruleParsing
     * @requires module:loggers
     * @requires module:data
     * @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants}
     * @requires {@link https://www.npmjs.com/package/path|path}
     * @author Seth Hollingsead
     * @date 2022/04/26
     * @copyright Copyright © 2022-… by Seth Hollingsead. All rights reserved
     */

    // Internal imports
    import dataBroker from '../../../brokers/dataBroker.js';
    import ruleParsing from '../ruleParsing.js';
    import loggers from '../../../executrix/loggers.js';
    import D from '../../../structures/data.js';
    // External imports
    import hayConst from '@haystacks/constants';
    import path from 'path';

    const {bas, biz, cfg, msg, sys, wrd} = hayConst;
    const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url));
    // framework.businessRules.rules.arrayParsing.dataArrayParsing.
    const namespacePrefix = wrd.cframework + bas.cDot + sys.cbusinessRules + bas.cDot + wrd.crules + bas.cDot + wrd.carray + wrd.cParsing + bas.cDot + baseFileName + bas.cDot;

    /**
     * @function arraysAreEqual
     * @description Determines if a set of arrays are equal or not.
     * @param {array<string|integer|boolean|float|object>} inputData The first array that should be checked for equality.
     * @param {array<string|integer|boolean|float|object>} inputMetaData The second array that should be checked for equality.
     * @return {boolean} True or False to indicate if the arrays are equal or not equal.
     * @author Seth Hollingsead
     * @date 2022/01/20
     * @NOTE: https://stackoverflow.com/questions/3115982/how-to-check-if-two-arrays-are-equal-with-javascript
     */
    async function arraysAreEqual(inputData, inputMetaData) {
      let functionName = arraysAreEqual.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
      let returnData = false;
      if (inputData && inputMetaData) {
        if (inputData === inputMetaData) { returnData = true; }
        if (inputData === null || inputMetaData === null) { returnData = false; }
        if (inputData.length != inputMetaData.length) { returnData = false; }
      } // End-if (inputData && inputMetaData)
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function storeData
     * @description Stores some data using the DataStorage data hie on the D data store.
     * @param {string} inputData The context name that the data should be stored with.
     * @param {string|integer|boolean|object|array} inputMetaData The data that should be stored.
     * @return {void}
     * @author Seth Hollingsead
     * @date 2022/01/20
     */
    async function storeData(inputData, inputMetaData) {
      let functionName = arraysAreEqual.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
      let returnData = false;
      if (inputData && inputMetaData) {
        await dataBroker.storeData(inputData, inputMetaData);
        returnData = true;
      } // End-if (inputData && inputMetaData)
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function getStoredData
     * @description Gets the named data stored in the D data structure in the DataStorage data hive.
     * @param {string} inputData The name of the sub-data hive that should contain the stored data we are looking for.
     * @param {string} inputMetaData Not used for this business rule.
     * @return {object} The data that was stored in the sub-data hive under the DataStorage data hive of the D data structure.
     * @author Seth Hollingsead
     * @date 2022/01/20
     */
    async function getStoredData(inputData, inputMetaData) {
      let functionName = getStoredData.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
      let returnData = false;
      if (inputData) {
        returnData = await dataBroker.getData(inputData);
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function isObjectEmpty
     * @description Determines if a JSON object is empty or not.
     * @param {object} inputData The object that should be checked for emptiness.
     * @param {string} inputMetaData Not used for this business rule.
     * @return {boolean} True or False to indicate if the object is empty or not empty.
     * @author Seth Hollingsead
     * @date 2022/01/21
     */
    async function isObjectEmpty(inputData, inputMetaData) {
      let functionName = isObjectEmpty.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = true;
      if (inputData) {
        for (let key in inputData) {
          if (Object.prototype.hasOwnProperty.call(inputData, key)) {
            returnData = false;
            // It may have a value, but is that value === null, if it is, reset back to true.
            if (inputData[key] === null) {
              returnData = true;
            }
          } // End-if (inputData.hasOwnProperty(key))
        } // End-for (let key in inputData)
      } // End-if (inputData)
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function isArrayEmpty
     * @description Determines if a JSON array is empty or not.
     * @param {array<string|integer|boolean|float|object>} inputData The array that should be checked for emptiness.
     * @param {string} inputMetaData Not used for this business rule.
     * @return {boolean} True or False to indicate if the array is empty or not empty.
     * @author Seth Hollingsead
     * @date 2022/01/21
     */
    async function isArrayEmpty(inputData, inputMetaData) {
      let functionName = isArrayEmpty.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
      let returnData = true;
      if (inputData) {
        returnData = await !Object.keys(inputData).length;
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function isObject
     * @description Determines if an object is a JSON object or not.
     * @param {object|array<string|integer|boolean|float|object>} inputData The object that should be tested to see if it is a JSON object or not.
     * @param {string} inputMetaData Not used for this business rule.
     * @return {boolean} True or False to indicate if the input object is an array or not.
     * @author Seth Hollingsead
     * @date 2022/01/21
     */
    async function isObject(inputData, inputMetaData) {
      let functionName = isObject.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = false;
      if (inputData) {
        if (typeof inputData === wrd.cobject) {
          returnData = true;
        }
      } // End-if (inputData)
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function isArray
     * @description Determines if an object is an array or not.
     * @param {object|array<string|integer|boolean|float|object>} inputData The object that
     * should be tested to see if it is an array or not.
     * @param {string} inputMetaData Not used for this business rule.
     * @return {boolean} True or False to indicate if the input object is an array or not.
     * @author Seth Hollingsead
     * @date 2022/01/21
     */
    async function isArray(inputData, inputMetaData) {
      let functionName = isArray.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = false;
      if (inputData) {
        returnData = await Array.isArray(inputData);
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function isArrayOrObject
     * @description Determines if an input object is either an array or a JSON object.
     * @param {object|array<string|integer|boolean|float|object>} inputData The object that
     * should be tested to see if it is either an array or a JSON object or not.
     * @param {string} inputMetaData Not used for this business rule.
     * @return {boolean} True or False to indicate if the input object is either an array or a JSON object.
     * @author Seth Hollingsead
     * @date 2022/01/21
     */
    async function isArrayOrObject(inputData, inputMetaData) {
      let functionName = isArrayOrObject.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = false;
      if (inputData) {
        if (await isObject(inputData, '') === true || await isArray(inputData, '') === true) {
          returnData = true;
        }
      } // End-if (inputData)
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function isNonZeroLengthArray
     * @description Determines if an object is an array of length greater than or equal to one or not.
     * @param {object|array<string|integer|boolean|float|object>} inputData The object/array that
     * should be tested to see if it is an array of length greater than or equal to 1 or not.
     * @param {string} inputMetaData Not used for this business rule.
     * @return {boolean} True or False to indicate if the input object is an array of length greater than equal to zero or not.
     * @author Seth Hollingsead
     * @date 2022/01/21
     */
    async function isNonZeroLengthArray(inputData, inputMetaData) {
      let functionName = isNonZeroLengthArray.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = false;
      if (inputData) {
        if (await isArray(inputData, '') === true && inputData.length >= 1) {
          returnData = true;
        }
      } // End-if (inputData)
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function arrayDeepClone
     * @description Clones an array by using JSON.stringify & JSON.parse.
     * Almost all other methods of cloning an array will actually clone by reference which essentially just clones the pointer to the array.
     * @NOTE: https://www.freecodecamp.org/news/how-to-clone-an-array-in-javascript-1d3183468f6a/
     * @param {array<string|integer|boolean|float|object>} inputData The array that should be deeply cloned.
     * @param {string} inputMetaData Not used for this business rule.
     * @return {array<string|integer|boolean|float|object>} The new array object after being cloned deeply.
     * @author Seth Hollingsead
     * @date 2022/01/21
     */
    async function arrayDeepClone(inputData, inputMetaData) {
      let functionName = arrayDeepClone.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
      let returnData = false;
      if (inputData && await isArray(inputData, '') === true && await isArrayEmpty(inputData, '') === false) {
        returnData = await JSON.parse(await JSON.stringify(inputData));
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function objectDeepClone
     * @description Recursively walks through all levels of a JSON object and deeply clones all of its contents including function objects.
     * @param {object} inputData The JSON object that should be deeply cloned.
     * @param {string} inputMetaData Not used for this business rule.
     * @return {object} A clone of the original input JSON object.
     * @author Seth Hollingsead
     * @date 2023/02/15
     * @NOTE This function was generated with the help of ChatGPT.
     */
    async function objectDeepClone(inputData, inputMetaData) {
      let functionName = objectDeepClone.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
      let returnData;
      let fastExit = false;
      if (typeof inputData !== wrd.cobject || inputData === null) {
        if (typeof inputData === wrd.cstring || typeof inputData === wrd.cnumber || typeof inputData === wrd.cboolean) {
          returnData = inputData;
          fastExit = true;
          // fast exit enabled
          await loggers.consoleLog(namespacePrefix + functionName, msg.cfastExitEnabled);
        } else {
          // Invalid input object. Expected a valid JSON object. Object type is:
          throw new Error(msg.cErrorObjectDeepCloneMessage01 + typeof inputData);
        }
      }
      if (fastExit === false) {
        if (Array.isArray(inputData)) {
          if (typeof inputData[1] === wrd.cobject) {
            // array cloning object
            await loggers.consoleLog(namespacePrefix + functionName, msg.carrayCloningObject);
            let cloneArray = [];
            for (let item in inputData) {
              cloneArray[item] = await objectDeepClone(inputData[item], '');
            }
            returnData = cloneArray;
          } else {
            // array deep clone string
            await loggers.consoleLog(namespacePrefix + functionName, msg.carrayDeepCloneString);
            returnData = await arrayDeepClone(inputData, '');
          }
        } else {
          // object deep cloning
          await loggers.consoleLog(namespacePrefix + functionName, msg.cobjectDeepCloning);
          returnData = {};
          for (let key in inputData) {
            if (Object.prototype.hasOwnProperty.call(inputData, key)) {
              if (typeof inputData[key] === wrd.cobject && inputData[key] !== null) {
                returnData[key] = await objectDeepClone(inputData[key], '');
              } else if (typeof inputData[key] === wrd.cfunction) {
                // Solution from: https://stackoverflow.com/questions/1833588/javascript-clone-a-function
                // const newFunction = inputData.bind(this);
                // Shallow copy any function properties if any.
                // returnData[key] = Object.assign(newFunction, inputData);
                returnData[key] = inputData[key];
              } else {
                returnData[key] = inputData[key];
              }
            } // End-if (inputData.hasOwnProperty(key))
          } // End-for (let key in inpuData)
        }
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function objectDeepMerge
     * @description Recursively deeply merges two objects that may or may not contains nested arrays.
     * @param {object} inputData The target data to be merged with.
     * @param {object} inputMetaData The data that should be merged.
     * @return {object} The merged data after the merge is complete.
     * @author Pery Mimon
     * @date 2020/04/23
     * @reference: https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge
     */
    async function objectDeepMerge(inputData, inputMetaData) {
      let functionName = objectDeepMerge.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
      let returnData = false;
      if (typeof inputData !== wrd.cobject || typeof inputMetaData !== wrd.cobject) {
        // inputData or inputMetaData or both ain't objets, merging doesn't make sense.
        returnData = false;
      } else {
        for (let property in inputMetaData) {
          if (!Object.prototype.hasOwnProperty.call(inputMetaData, property)) {
            continue; // Take into consideration only object's own properties.
          }
          // property is:
          await loggers.consoleLog(namespacePrefix + functionName, msg.cpropertyIs + JSON.stringify(property));
          if (property in inputData) {
            // property is in inputData
            await loggers.consoleLog(namespacePrefix + functionName, msg.cobjectDeepMergeMessage01);
            // inputData[property] is:
            await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataPropertyIs + JSON.stringify(inputData[property]));
            // Handling merging of two properties with equal names.
            if (typeof inputData[property] !== wrd.cobject) {
              // inputData[property] is not an object!
              // Assign it directly.
              await loggers.consoleLog(namespacePrefix + functionName, msg.cobjectDeepMergeMessage02 + bas.cSpace + msg.cobjectDeepMergeMessage03);
              inputData[property] = inputMetaData[property];
            } else {
              // inputData[property] is an object!
              await loggers.consoleLog(namespacePrefix + functionName, msg.cobjectDeepMergeMessage04);
              // inputMetaData[property] is:
              await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataPropertyIs + JSON.stringify(inputMetaData[property]));
              if (typeof inputMetaData[property] !== wrd.cobject) {
                // inputMetaData[property] is not an object,
                // Assign it directly.
                await loggers.consoleLog(namespacePrefix + functionName, msg.cobjectDeepMergeMessage05 + msg.cobjectDeepMergeMessage03);
                inputData[property] = inputMetaData[property];
              } else {
                // inputMetaData[property] is an object.
                await loggers.consoleLog(namespacePrefix + functionName, msg.cobjectDeepMergeMessage06);
                if (inputData[property].concat && inputMetaData[property].concat) {
                  // Are the arrays length 1 or greater?
                  if (inputData[property].length === 1 && inputMetaData[property].length === 1) {
                    // Array lengths are the same at this level.
                    await loggers.consoleLog(namespacePrefix + functionName, msg.cobjectDeepMergeMessage07);
                    // We should deeply merge the contents of the arrays.
                    inputData[property] = await objectDeepMerge(inputData[property], inputMetaData[property]);
                  } else {
                    // Two arrays get concatenated.
                    await loggers.consoleLog(namespacePrefix + functionName, msg.cobjectDeepMergeMessage08);
                    inputData[property] = inputData[property].concat(inputMetaData[property]);
                    // AFTER concatenating two arrays: inputData[property] is:
                    await loggers.consoleLog(namespacePrefix + functionName, msg.cAfterConcatenating2ArraysInputDataPropertyIs + JSON.stringify(inputData[property]));
                  }
                } else {
                  // Two objects get merged recursively.
                  await loggers.consoleLog(namespacePrefix + functionName, msg.cobjectDeepMergeMessage09);
                  inputData[property] = await objectDeepMerge(inputData[property], inputMetaData[property]);
                  // AFTER recursive merge: inputData[property] is:
                  await loggers.consoleLog(namespacePrefix + functionName, msg.cAfterRecursiveMergeInputDataPropertyIs + JSON.stringify(inputData[property]));
                }
              }
            }
          } else {
            // property is not in inputData,
            // so add it directly.
            await loggers.consoleLog(namespacePrefix + functionName, msg.cobjectDeepMergeMessage10 + msg.cobjectDeepMergeMessage11);
            inputData[property] = inputMetaData[property];
          }
        } // End-for (let property in inputMetaData)
        returnData = inputData;
      }

      // NOTE: This implementation from Vincent on the reference: Dated: 2019/11/19
      // https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge
      // Did not work:
      // inputData was: {"CommandWorkflows":{"businessRules":[{"arrayParsing":[{"characterArrayParsing":[{"Workflow":[{"$":{"Name":"doesArrayContainCharacter","Value":"cmdgen bizRul,doesArrayContainCharacter,$,[the|answer|to|life|the|universe|and|everything|is|$42] 4"}},{"$":{"Name":"removeCharacterFromArray","Value":"cmdgen bizRul,removeCharacterFromArray,$,[the|answer|to|life|the|universe|and|everything|is|$42] 4"}},{"$":{"Name":"replaceCharacterAtIndex","Value":"cmdgen bizRul,replaceCharacterAtIndex,aggregateNumericalDifferenceBetweenTwoStrings,[10|$] 4"}},{"$":{"Name":"characterArrayParsing","Value":"cmdSeq wrkflo,doesArrayContainCharacter wrkflo,removeCharacterFromArray wrkflo,replaceCharacterAtIndex"}}]}]}]}]}}
      //
      // inputMetaData was: {"CommandWorkflows":{"businessRules":[{"arrayParsing":[{"pathArrayParsing":[{"Workflow":[{"$":{"Name":"doesArrayContainFilename","Value":"cmdgen bizRul,doesArrayContainFilename,[20200603-142834-763_0.3.2_haystacks.zip|20200603-144529-749_0.3.3_haystacks.zip|20200604-133509-704_0.4.0_haystacks.zip|20200604-133546-749_0.4.1_haystacks.zip|20200604-160655-262_0.4.2_haystacks.zip|20200604-161819-191_0.4.3_haystacks.zip|20200604-173727-348_0.4.4_haystacks.zip|20200604-193551-258_0.5.0_haystacks.zip|20200604-193629-853_0.5.1_haystacks.zip|20200605-083055-193_0.5.2_haystacks.zip|20200610-170634-141_0.5.3_haystacks.zip|20200615-162658-576_0.5.4_haystacks.zip|20200617-150430-416_0.5.5_haystacks.zip|20200618-134424-575_0.5.6_haystacks.zip],20200604-193551-258_0.5.0_haystacks.zip 4"}},{"$":{"Name":"pathArrayParsing","Value":"cmdSeq wrkflo,doesArrayContainFilename"}}]}]}]}]}}
      //
      // returnData was: {"CommandWorkflows":{"businessRules":[{"arrayParsing":[{"characterArrayParsing":[{"Workflow":[{"$":{"Name":"doesArrayContainCharacter","Value":"cmdgen bizRul,doesArrayContainCharacter,$,[the|answer|to|life|the|universe|and|everything|is|$42] 4"}},{"$":{"Name":"removeCharacterFromArray","Value":"cmdgen bizRul,removeCharacterFromArray,$,[the|answer|to|life|the|universe|and|everything|is|$42] 4"}},{"$":{"Name":"replaceCharacterAtIndex","Value":"cmdgen bizRul,replaceCharacterAtIndex,aggregateNumericalDifferenceBetweenTwoStrings,[10|$] 4"}},{"$":{"Name":"characterArrayParsing","Value":"cmdSeq wrkflo,doesArrayContainCharacter wrkflo,removeCharacterFromArray wrkflo,replaceCharacterAtIndex"}}]}]}]}]}}
      //
      // Clearly it didn't do it's job!!
      // **************************************************
      // if (inputData && inputMetaData) {
      //   for (let key of Object.entries(inputMetaData)) {
      //     if (!inputData.hasOwnProperty(key) || typeof inputMetaData[key] !== wrd.cobject) { inputData[key] = inputMetaData[key]; }
      //     else if (inputData[key] instanceof Array && inputMetaData[key] instanceof Array) {inputData[key] = inputData[key].concat(inputMetaData[key]); }
      //     else { objectDeepMerge(inputData[key], inputMetaData[key]); }
      //   } // End-for (key of Object.keys(inputMetaData))
      //   returnData = inputData;
      // } // End-if (inputData && inputMetaData)
      // ***************************************************

      // NOTE: This implementation from Pery Mimon on the reference: Dated 2020/04/23
      // https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge
      // Did not work:
      // Throws an error: inputMetaData is not iterable
      // **************************************************
      // if (inputData && inputMetaData) {
      //   for (source of inputMetaData) {
      //     for (let k in source) {
      //       let vs = source[k], vt = inputData[k];
      //       if (Object(vs) === vs && Object(vt) === vt) {
      //         inputData[k] = objectDeepMerge(vt, vs);
      //         continue;
      //       }
      //       inputData[k] = source[k];
      //     } // End-for (let k in source)
      //   } // End-for (source of inputMetaData)
      //   returnData = inputData;
      // } // End-if (inputData && inputMetaData)
      // **************************************************

      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function getNamespacedDataObject
     * @description Navigates the D data structure JSON data object tree to find the namespace of data settings.
     * @param {array<string>} inputData The path in the data JSON object where the
     * setting should be returned.
     * @param {boolean} inputMetaData True or False value to indicate if
     * the path elements should be created or not it they are not found.
     * @return {object|boolean} The object found at the specified namespace address in the data object,
     * or False if nothing was found.
     * @author Seth Hollingsead
     * @date 2022/05/10
     * @NOTE This function contains duplicate code from the configurator.js,
     * however, we cannot call this business rule from the configurator because
     * the configurator gets called before the business rules are bootstrapped.
     * And there are protections in place, but rules cannot be called if they are not yet bootstrapped.
     * So therefore this code will need to remain duplicated.
     * And by the time this gets called everything should be effectively bootstrapped.
     * Therefore we can use the loggers here.
     */
    async function getNamespacedDataObject(inputData, inputMetaData) {
      let functionName = getNamespacedDataObject.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = false;
      let processingValidData = false;
      let namespaceDataObject = D;
      if (inputData && inputData.length > 0) {
        for (const element of inputData) {
          processingValidData = true;
          if (!namespaceDataObject[element] && inputMetaData === true) {
            // It doesn't exist yet, so lets make it.
            namespaceDataObject[element] = {};
          } else if (!namespaceDataObject[element]) {
            console.log(msg.cnamespaceDataObjectPathNotFound + JSON.stringify(element));
            processingValidData = false;
            break;
          }
          namespaceDataObject = namespaceDataObject[element];
        } // End-for (const element of inputData)
        if (processingValidData === true) {
          returnData = namespaceDataObject;
        }
      } // End-if (inputData && inputData.length > 0)
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function setNamespacedDataObject
     * @description Persists a change to the data structure.
     * @param {array<string>} inputData The path in the data JSON object where the
     * setting should be persisted.
     * @param {object} inputMetaData The data to be persisted on the D-data structure.
     * @return {boolean} True or False to indicate if the data was persisted correctly or not.
     * @author Seth Hollingsead
     * @date 2022/05/11
     */
    async function setNamespacedDataObject(inputData, inputMetaData) {
      let functionName = setNamespacedDataObject.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
      let returnData = false;
      let namespaceDataObject = D;
      if (inputData && inputData.length > 0) {
        for (let i = 0; i < inputData.length - 1; i++) {
          namespaceDataObject = namespaceDataObject[inputData[i]];
          if (i === inputData.length - 2) {
            // namespaceDataObject is:
            await loggers.consoleLog(namespacePrefix + functionName, msg.cnamespaceDataObjectIs + JSON.stringify(namespaceDataObject));
            let fullyQualifiedKey = await namespaceDataObject.join(bas.cDot);
            if (await ruleParsing.processRulesInternal([[namespaceDataObject, cfg.cdebugSetting], await ruleParsing.getRule(biz.cascertainMatchingElements)], [biz.cdoesArrayContainValue]) === true) {
              namespaceDataObject[fullyQualifiedKey] = inputMetaData;
            } else {
              namespaceDataObject[inputData[i + 1]] = inputMetaData;
            }
            returnData = true;
          } // End-if (i === inputData.length - 2)
        } // End-for (let i = 0; i < inputData.length - 1; i++)
      } // End-if (inputData && inputData.length > 0)
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    export default {
      arraysAreEqual,
      storeData,
      getStoredData,
      isObjectEmpty,
      isArrayEmpty,
      isObject,
      isArray,
      isArrayOrObject,
      isNonZeroLengthArray,
      arrayDeepClone,
      objectDeepClone,
      objectDeepMerge,
      getNamespacedDataObject,
      setNamespacedDataObject
    };