Home Reference Source Test

src/Object/index.js

import { isArray, isNotNull, isPrimitive, isObject } from '../Type';
import { and } from '../Predicate-Combinator';

/**
 * @desc Will deep frezee all properties.
 * @param {object} obj Object that should be fully freezed.
 * @return {object} Freezed object.
 */
export function deepFreeze(obj) {
  let propNames = Object.getOwnPropertyNames(obj);

  propNames.forEach(name => {
    let prop = obj[name];

    if (and(isObject, isNotNull)(prop))
      deepFreeze(prop);
  });

  return Object.freeze(obj);
};

/**
 * @desc Will create object clone.
 * @param {object} objectToClone Object that should be cloned.
 * @return {object} Clone of passed object.
 */
export function cloneObject(objectToClone) {
  return Object.assign({}, objectToClone);
};

/**
 * @desc Will create an object deep clone.
 * @param {object} objectToDeepClone Object that should be deep cloned.
 * @return {object} Clone of passed object.
 */
export function deepClone(objectToDeepClone) {
  let newObj = objectToDeepClone;
  if (objectToDeepClone && typeof objectToDeepClone === 'object') {
      newObj = isArray(objectToDeepClone) ? [] : {};
      for (let i in objectToDeepClone) {
          newObj[i] = deepClone(objectToDeepClone[i]);
      }
  }
  return newObj;
};

/**
 * @desc Will create and return a new object with
 * selected properties from pickArray.
 * @param {object} source Object that should have updated structure.
 * @param {...string} pickArray Properties that should be included in
 * result object.
 * @return {object} Object that includes pickArray property list.
 */
export function pick(source, ...pickArray) {
  return pickArray.reduce((accumulator, sourceKey) => {
    accumulator[sourceKey] = source[sourceKey]
    return accumulator;
  }, {});
};

/**
 * @desc Will create and return a new object with omited properties from omitArray.
 * @param {object} source Object that should have updated structure.
 * @param {...string} omitArray Properties that should omited in result object.
 * @return {object} Object with excluded properties from omitArray.
 */
export function omit(source, ...omitArray) {
  return Object.keys(source).reduce((accumulator, sourceKey) => {
    if (!omitArray.includes(sourceKey))
      accumulator[sourceKey] = source[sourceKey]
    return accumulator;
  }, {});
};

/**
 * @desc Implementation of optional chain operator.
 * @param {object} obj Context object.
 * @param {...string} props Property chain.
 * @return Context value by property chain.
 */
export function safeGet(obj, ...props) {
  let val = obj[props.shift()];
  return (props.length && val) ? safeGet(val, ...props) : val;
};

/**
 * @desc Implementation of optional chain operator.
 * @param {object} obj Context object.
 * @param {any} orValue Value which will be returned if obj doesn't
 * contain truthly value.
 * @param {...string} props Property chain.
 * @return {any} Context value by property chain or orValue.
 */
export function safeGetOr(obj, orValue, ...props) {
  return safeGet(obj, ...props) || orValue;
};

/**
 * @desc Curry version of get function that expects in
 * future to get context.
 * @param {...string} props Property chain.
 * @return {Function} Curry functions that expects to get
 * context object.
 */
export function getWithProps(...props) {
  return obj => safeGet(obj, ...props);
};

/**
 * @desc Curry version of get function that expects in
 * future to get property list.
 * @param {object} obj Context object.
 * @return {Function} Curry functions that expects to get
 * property list.
 */
export function getWithCtx(obj) {
  return (...props) => safeGet(obj, ...props);
};

/**
 * @desc Iterates by object properties.
 * @param {object} obj Context object.
 * @param {Function} cb Callback function which will be invoked
 * with key and value argument.
 * @param {Function} skipCb Skip function which allows to skip property
 * by given condition.
 * @return {Array<any>} Returns array projected by Callback function.
 */
export function objectProjection(obj, cb, skipCb = (...args) => args) {
  return Object.entries(obj)
    .filter(([k, v]) => skipCb(k, v))
    .map(([key, value]) =>
      cb(key, value));
}

/**
 * @desc Checks object properties with set of predicates.
 * @example
 * let given = { b: 1, a: 'Hello' };
 *
 * conformsTo(given, { b: n => n === 1, a: n => n === 'Hello' }); // true
 *
 * conformsTo(given, { b: n => n === 1, a: n => n === 'NOT-Hello' }); // false
 *
 * @param {object} object Object which should be chcked.
 * @param {object} predicateSetModel Predicate set model.
 * @returns {boolean} Returns true if all predicates returns true.
 */
export function conformsTo(object, predicateSetModel) {
  return Object.keys(predicateSetModel)
    .every(key => predicateSetModel[key](object[key]))
}

/**
 * @desc Compares two elements.
 * @example
 * deepEquals(
 *   [
 *     { id: 1, firstName: 'Jon', lastName: 'Snow', age: 14, location: 'Winterfell' },
 *     { id: 2, firstName: 'Eddard', lastName: 'Stark', age: 35, location: 'Winterfell' },
 *     { id: 3, firstName: 'Catelyn', lastName: 'Stark', age: 33, location: 'Winterfell' },
 *     { id: 4, firstName: 'Roose', lastName: 'Bolton', age: 40, location: 'Dreadfort' },
 *     { id: 5, firstName: 'Ramsay', lastName: 'Snow', age: 15, location: 'Dreadfort' }
 *   ],
 *   [
 *     { id: 1, firstName: 'Jon', lastName: 'Snow', age: 14, location: 'Winterfell' },
 *     { id: 2, firstName: 'Eddard', lastName: 'Stark', age: 35, location: 'Winterfell' },
 *     { id: 3, firstName: 'Catelyn', lastName: 'Stark', age: 33, location: 'Winterfell' },
 *     { id: 4, firstName: 'Roose', lastName: 'Bolton', age: 40, location: 'Dreadfort' },
 *     { id: 5, firstName: 'Ramsay', lastName: 'Snow', age: 15, location: 'Dreadfort' }
 *   ]
 * ); // true
 *
 * deepEquals(
 *   [
 *     { id: 1, firstName: 'Jon', lastName: 'Snow', age: 14, location: 'Winterfell' },
 *     { id: 2, firstName: 'Eddard', lastName: 'Stark', age: 35, location: 'Winterfell' },
 *     { id: 3, firstName: 'Catelyn', lastName: 'Stark', age: 33, location: 'Winterfell' },
 *     { id: 4, firstName: 'Roose', lastName: 'Bolton', age: 40, location: 'Dreadfort' },
 *     { id: 5, firstName: 'Ramsay', lastName: 'Snow', age: 15, location: 'Dreadfort' }
 *   ],
 *   [
 *     { id: 1, firstName: 'Jon', lastName: 'Snow', age: 14, location: 'Winterfell' },
 *     { id: 2, firstName: 'Eddard', lastName: 'Stark', age: 35, location: 'Winterfell' },
 *     { id: 3, firstName: 'Catelyn', lastName: 'Stark', age: 33, location: 'Winterfell' },
 *     { id: 4, firstName: 'Roose', lastName: 'Bolton', age: 40, location: 'Dreadfort' },
 *     { id: 5, firstName: 'Not the same!', lastName: 'Snow', age: 15, location: 'Dreadfort' }
 *   ]
 * ); // false
 * @param {any} first First element for comparation.
 * @param {any} second Second element for comparation.
 * @returns {boolean} Returns true if two elements are the same.
 */
export function deepEquals(first, second) {
  if (isPrimitive(first) && isPrimitive(second)) return first === second;
  if (isPrimitive(first) || isPrimitive(second)) return false;

  let firstKeys = Object.keys(first);

  if(firstKeys.length !== Object.keys(second).length) return false;

  return firstKeys.every(key => deepEquals(first[key], second[key]));
};