text: 'use strict';
  
  const common = require('./common.js');
  
  /**
   * @module util/object
   * @desc Utility functions for object.
   */
  
  /**
   * Iterates over own enumerable string keyed properties of an object
   * and invokes iteratee for each property.
   * The iteratee is invoked with three arguments: (value, key, object).
   * @param {!Object<string, any>} obj - The object to iterate over.
   * @param {Function} iteratee - The function invoked per iteration.
   * @static
   */
  const forOwn = (obj, iteratee = common.identity) => {
    for (let prop in obj) {
      if (obj.hasOwnProperty(prop)) {
        iteratee(obj[prop], prop, obj);
      }
    }
  };
  
  /**
   * Get enumerable values of an object. Optionally map each value by transform function.
   * @param {!Object<string, any>} obj - The object to iterate over.
   * @param {Function} transform - Transform function applied to each value.
   * @return {Array} - Array of transformed values.
   * @static
   */
  const values = (obj, transform = common.identity) =>
    Object.keys(obj).map((key) => transform(obj[key]));
  
  /**
   * Get enumerable kays and values as tuple pairs.
   * @param {!Object<string, any>} obj - The object to iterate over.
   * @return {Array} - Array of key and value tuples.
   * @static
   */
  const entries = (obj) => Object.keys(obj).map((key) => [key, obj[key]]);
  
  /**
   * Creates an object with the same values as object and keys generated by running
   * each own enumerable string keyed property of object thru iteratee.
   * The iteratee is invoked with three arguments: (value, key, object).
   * @param {Object<string, any>} obj - The object to iterate over.
   * @param {Function} iteratee - The function invoked per iteration.
   * @return {Object<string, any>} - Returns the new mapped object.
   * @static
   */
  const mapKeys = (obj = {}, iteratee = common.identity) => {
    const result = {};
    forOwn(obj, (val, key) => {
      result[iteratee(val, key, obj)] = val;
    });
    return result;
  };
  
  /**
   * Creates an object with the same keys as object and values generated by running
   * each own enumerable string keyed property of object thru iteratee.
   * The iteratee is invoked with three arguments: (value, key, object).
   * @param {Object<string, any>} obj - The object to iterate over.
   * @param {Function} iteratee - The function invoked per iteration.
   * @return {Object<string, any>} - Returns the new mapped object.
   * @static
   */
  const mapValues = (obj = {}, iteratee = common.identity) => {
    const result = {};
    forOwn(obj, (val, key) => {
      result[key] = iteratee(val, key, obj);
    });
    return result;
  };
  
  /**
   * Creates an object composed of the object properties predicate returns truthy for.
   * The predicate is invoked with two arguments: (value, key).
   * @param {Object<string, any>} obj - The source object.
   * @param {Function} predicate - The function invoked per property.
   * @return {Object<string, any>} - Returns the new object.
   * @static
   */
  const pickBy = (obj = {}, predicate = common.identity) => {
    const result = {};
    forOwn(obj, (val, key) => {
      if (predicate(val, key)) {
        result[key] = val;
      }
    });
    return result;
  };
  
  /**
   * Deep cleans an object or array to remove empty values.
   * @param {!Object<string, any>|Array} input - Input to be cleaned.
   * @return {Object<string, any>|Array} - Cleaned input.
   * @static
   */
  const deepClean = (input) => (Array.isArray(input) ? arrayClean(input) : objClean(input));
  
  /**
   * Deep cleans an array to remove elements with empty values.
   * @param {Array} arr - Array to be cleaned.
   * @return {Array} - Cleaned Array.
   * @static
   */
  const arrayClean = (arr) => {
    return arr.reduce((acc, ele) => {
      if (!isEmpty(ele)) {
        if (typeof ele !== 'object') {
          acc.push(ele);
        } else {
          const cleanEle = deepClean(ele);
          if (!isEmpty(cleanEle)) {
            acc.push(cleanEle);
          }
        }
      }
      return acc;
    }, []);
  };
  
  /**
   * Deep cleans an object to remove props with empty values.
   * @param {!Object<string, any>} obj - Object to be cleaned.
   * @return {Object<string, any>} - Cleaned object.
   * @static
   */
  const objClean = (obj) => {
    const cleaned = {};
    forOwn(obj, (val, prop) => {
      if (!isEmpty(val)) {
        if (typeof val !== 'object') {
          cleaned[prop] = val;
        } else {
          const cleanVal = deepClean(val);
          if (!isEmpty(cleanVal)) {
            cleaned[prop] = cleanVal;
          }
        }
      }
    });
    return cleaned;
  };
  
  /**
   * Checks if input target is an empty value ({}, [], '', undefined, null).
   * @param {*} target - Target to verify.
   * @return {boolean} - True if target is empty value.
   * @static
   */
  const isEmpty = (target) => target === '' || target == null || isEmptyObj(target);
  
  /**
   * Checks if input target is an empty object.
   * @param {*} target - Target to verify.
   * @return {boolean} - True if input is an empty object.
   * @static
   */
  const isEmptyObj = (target) => isObject(target) && Object.keys(target).length === 0;
  
  /**
   * Checks if input target is a non-null object.
   * @param {*} target - Target to verify.
   * @return {boolean} - True if input is a non-null object.
   * @static
   */
  const isObject = (target) => target === Object(target);
  
  /**
   * Stringify object and optionally exclude specific keys' values.
   * @param {Object} obj - Target to stringify.
   * @param  {...string} exclude - List of keys to exclude from resolving stringified value.
   * @return {string} - Stringified target.
   */
  const stringify = (obj, ...exclude) => {
    const excluded = new Set(exclude);
    const filtered = Object.keys(obj).reduce((out, key) => {
      if (excluded.has(key)) {
        out[key] = '[Excluded]';
      } else {
        const value = obj[key];
        try {
          JSON.stringify(value);
          out[key] = value;
        } catch (err) {
          out[key] =
            err.message === 'Converting circular structure to JSON'
              ? '[Circular]'
              : `[Stringify Error] ${err}`;
        }
      }
      return out;
    }, {});
    return JSON.stringify(filtered, null, 2);
  };
  
  module.exports = {
    forOwn,
    values,
    entries,
    mapKeys,
    mapValues,
    pickBy,
    deepClean,
    arrayClean,
    objClean,
    isEmpty,
    isEmptyObj,
    isObject,
    stringify,
  };
  