@orbit/utils
Version: 
Core utilities for Orbit.
234 lines (216 loc) • 5.52 kB
text/typescript
/* eslint-disable @typescript-eslint/explicit-module-boundary-types, valid-jsdoc */
/**
 * Clones a value. If the value is an object, a deeply nested clone will be
 * created.
 *
 * Traverses all object properties (but not prototype properties).
 */
export function clone(obj: any): any {
  if (obj === undefined || obj === null || typeof obj !== 'object') {
    return obj;
  }
  let dup: any;
  let type = Object.prototype.toString.call(obj);
  if (type === '[object Date]') {
    dup = new Date();
    dup.setTime(obj.getTime());
  } else if (type === '[object RegExp]') {
    dup = obj.constructor(obj);
  } else if (type === '[object Array]') {
    dup = [];
    for (let i = 0, len = obj.length; i < len; i++) {
      if (obj.hasOwnProperty(i)) {
        dup.push(clone(obj[i]));
      }
    }
  } else {
    let val;
    dup = {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        val = obj[key];
        if (typeof val === 'object') {
          val = clone(val);
        }
        dup[key] = val;
      }
    }
  }
  return dup;
}
/**
 * Expose properties and methods from one object on another.
 *
 * Methods will be called on `source` and will maintain `source` as the context.
 *
 * @deprecated since v0.17
 */
export function expose(destination: any, source: any): void {
  let properties: string[];
  if (arguments.length > 2) {
    properties = Array.prototype.slice.call(arguments, 2);
  } else {
    properties = Object.keys(source);
  }
  properties.forEach((p) => {
    if (typeof source[p] === 'function') {
      destination[p] = function () {
        return source[p].apply(source, arguments);
      };
    } else {
      destination[p] = source[p];
    }
  });
}
/**
 * Extend an object with the properties of one or more other objects.
 *
 * @deprecated since v0.17
 */
export function extend(destination: any, ...sources: any[]): any {
  sources.forEach((source) => {
    for (let p in source) {
      if (source.hasOwnProperty(p)) {
        destination[p] = source[p];
      }
    }
  });
  return destination;
}
/**
 * Converts an object to an `Array` if it's not already.
 *
 * @export
 * @param {*} obj
 * @returns {any[]}
 */
export function toArray(obj: unknown): any[] {
  if (isNone(obj)) {
    return [];
  } else {
    return Array.isArray(obj) ? obj : [obj];
  }
}
/**
 * Checks whether a value is a non-null object
 *
 * @export
 * @param {*} obj
 * @returns {boolean}
 */
export function isObject(obj: unknown): boolean {
  return obj !== null && typeof obj === 'object';
}
/**
 * Checks whether an object is null or undefined
 *
 * @export
 * @param {*} obj
 * @returns {boolean}
 */
export function isNone(obj: unknown): boolean {
  return obj === undefined || obj === null;
}
/**
 * Merges properties from other objects into a base object. Properties that
 * resolve to `undefined` will not overwrite properties on the base object
 * that already exist.
 *
 * @deprecated since v0.17
 */
export function merge(object: any, ...sources: any[]): any {
  sources.forEach((source) => {
    Object.keys(source).forEach((field) => {
      if (source.hasOwnProperty(field)) {
        let value = source[field];
        if (value !== undefined) {
          object[field] = clone(value);
        }
      }
    });
  });
  return object;
}
/**
 * Merges properties from other objects into a base object, traversing and
 * merging any objects that are encountered.
 *
 * Properties that resolve to `undefined` will not overwrite properties on the
 * base object that already exist.
 */
export function deepMerge(object: any, ...sources: any[]): any {
  sources.forEach((source) => {
    Object.keys(source).forEach((field) => {
      if (source.hasOwnProperty(field)) {
        let a = object[field];
        let b = source[field];
        if (
          isObject(a) &&
          isObject(b) &&
          !Array.isArray(a) &&
          !Array.isArray(b)
        ) {
          deepMerge(a, b);
        } else if (b !== undefined) {
          object[field] = clone(b);
        }
      }
    });
  });
  return object;
}
/**
 * Retrieves a value from a nested path on an object.
 *
 * Returns any falsy value encountered while traversing the path.
 */
export function deepGet(obj: any, path: string[]): any {
  let index = -1;
  let result = obj;
  while (++index < path.length) {
    result = result[path[index]];
    if (!result) {
      return result;
    }
  }
  return result;
}
/**
 * Sets a value on an object at a nested path.
 *
 * This function will create objects along the path if necessary to allow
 * setting a deeply nested value.
 *
 * Returns `false` only if the current value is already strictly equal to the
 * requested `value` argument. Otherwise returns `true`.
 */
export function deepSet(obj: any, path: string[], value: any): boolean {
  let ptr = obj;
  let prop = path.pop() as string;
  let segment;
  for (let i = 0, l = path.length; i < l; i++) {
    segment = path[i];
    if (ptr[segment] === undefined) {
      ptr[segment] = typeof segment === 'number' ? [] : {};
    }
    ptr = ptr[segment];
  }
  if (ptr[prop] === value) {
    return false;
  } else {
    ptr[prop] = value;
    return true;
  }
}
/**
 * Find an array of values that correspond to the keys of an object.
 *
 * This is a ponyfill for `Object.values`, which is still experimental.
 */
export function objectValues(obj: any): any[] {
  if (Object.values) {
    return Object.values(obj);
  } else {
    return Object.keys(obj).map((k) => obj[k]);
  }
}