Object.js

/**
 * @namespace Object
 */

/**
 * Returns verdict if given subject is Object or not
 *
 * @memberof Object
 * @param {*} subject Subject of examination
 * @returns {boolean} Verdict
 */
function isObject(subject) {
  return subject !== null && typeof subject === 'object';
}

/**
 * Copy key and values from src Object to dst  Object
 *
 * @memberof Object
 * @param {Object} src Source
 * @param {Object} dst Destination
 * @param {(Array|null)} [what] What should be copied? By default, those are all keys and values from source
 * @returns {void}
 */
function copy(src, dst, what) {
  let i;
  if(what == null || what.length === 0) {
    for(i in src) {
      dst[i] = src[i];
    }
  } else {
    for(i = 0; i < what.length; i++) {
      dst[what[i]] = src[what[i]];
    }
  }
}

/**
 * Returns verdict if subject match query
 *
 * @memberof Object
 * @param {Object} subject Subject of examination
 * @param {Object|*} query Query - if object then will compare each key of query with subject,
 * otherwise will just use standard comparator
 * @returns {boolean} Verdict
 */
function isLike(subject, query) {
  let k, v;
  if(typeof query == 'object' && typeof subject == 'object') {
    for(k in query) {
      v = typeof subject[k] == 'function' ? subject[k]() : subject[k];
      if(v !== query[k]) {
        return false;
      }
    }
  } else {
    if(subject !== query) {
      return false;
    }
  }
  return true;
}

/**
 * Count number of items in given subject
 *
 * @memberof Object
 * @param {Object} subject Subject of examination
 * @returns {number} Number of items
 */
function count(subject) {
  let items = 0;
  for(const i in subject) {
    if(subject.hasOwnProperty(i)) {
      items += 1;
    }
  }
  return items;
}

/**
 * Polyfill for ECMAScript 2017 for Object.values
 * https://www.ecma-international.org/ecma-262/8.0/#sec-object.values
 *
 * @memberof Object
 */
function values(o) {
  const obj = Object(o);
  const values = [];

  for(const k in obj) {
    values.push(obj[k]);
  }

  return values;
}

/**
 * Polyfill for ECMAScript 2015 for Object.assign
 * https://www.ecma-international.org/ecma-262/6.0/#sec-object.assign
 *
 * @memberof Object
 */
function assign() {
  const args = Array.prototype.slice.call(arguments, 0);
  const to = Object(args[0]);

  if(args.length !== 1) {
    const sources = args.slice(1);
    let nextSource, keys, from, nextKey, propValue;

    for(let i = 0; i < sources.length; i++) {
      nextSource = sources[i];
      from = Object(nextSource);

      if(typeof nextSource === 'undefined' || nextSource === null) {
        keys = [];
      } else {
        keys = Object.keys(from);
      }

      for(let j = 0; j < keys.length; j++) {
        nextKey = keys[j];
        propValue = from[nextKey];
        if(typeof propValue !== 'undefined' && from.propertyIsEnumerable(nextKey)) {
          to[nextKey] = propValue;
        }
      }
    }
  }

  return to;
}

/**
 * This is similar to Object.assign, but extends also deep nested Objects
 *
 * @memberof Object
 * @returns {object} Object
 */
function deepAssign() {
  const args = Array.prototype.slice.call(arguments, 0);
  const to = Object(args[0]);

  if(args.length !== 1) {
    const sources = args.slice(1);
    let nextSource, keys, from, nextKey, propValue;

    for(let i = 0; i < sources.length; i++) {
      nextSource = sources[i];
      from = Object(nextSource);

      if(typeof nextSource === 'undefined' || nextSource === null) {
        keys = [];
      } else {
        keys = Object.keys(from);
      }

      for(let j = 0; j < keys.length; j++) {
        nextKey = keys[j];
        propValue = from[nextKey];
        if(typeof propValue !== 'undefined' && from.propertyIsEnumerable(nextKey)) {
          if(typeof to[nextKey] === 'object' && typeof propValue === 'object') {
            const areArrays = Array.isArray(to[nextKey]) && Array.isArray(propValue);

            to[nextKey] = Object.deepAssign({}, to[nextKey], propValue);

            if(areArrays) {
              to[nextKey] = Object.values(to[nextKey]);
            }
          } else {
            to[nextKey] = propValue;
          }
        }
      }
    }
  }

  return to;
}

/**
 * Clone an Object
 * @param {Object} o Object to clone
 */
function clone(o) {
  return JSON.parse(JSON.stringify(o));
}


export default {
  static: {
    isObject,
    copy,
    isLike,
    count,
    values,
    assign,
    deepAssign,
    clone
  }
};