collection/get.ts

/** @module Collection */

import { exists } from '@ext/exists'
import { isArr } from '@array/isArr'
import { isStr } from '@string/isStr'

/**
 * Searches an object based on the path param
 * <br/>I.E. path = 'data.foo.bar' => will return obj.data.foo.bar.
 * <br/>If bar does not exist, then will return obj.data.foo
 * @example
 * get(obj, 'data.foo.bar')
 * // Returns the value of bar
 * @example
 * get(obj, ['data', 'foo', 'bar'])
 * // Returns the value of bar
 * @function
 * @param {Object} obj - Will search the object based on the path
 * @param {String|Array<string>} path - Dot notation string or Array of string keys of the object
 * @param {*} [fallback] - Separated string to search the object
 * @return {*} - The final value found from the path
 */
export const get = <T = any>(
  obj: Record<any, any> | any[],
  path: string | string[],
  fallback?: T
): T => {
  const isPathArr = isArr(path)
  if (!isStr(path) && !isPathArr) return exists(fallback) ? fallback : undefined

  const parts = isPathArr ? path : path.split('.')

  const result = parts.reduce((obj, prop) => {
    const type = typeof obj
    if (!exists(obj) || (type !== 'object' && type !== 'function'))
      return undefined

    prop = prop.startsWith('[') ? prop.replace(/\D/g, '') : prop
    return obj[prop] as T
  }, obj)

  return exists(result) ? (result as T) : (fallback as T)
}