Daily-Question icon indicating copy to clipboard operation
Daily-Question copied to clipboard

【Q198】如何实现类似 lodash.get 函数

Open shfshanyue opened this issue 4 years ago • 7 comments

使用 get 函数可避免长链的 key 时获取不到属性而出现问题,此时进行异常避免时及其服务,如 o.a && o.a.b && o.a.b.c && o.a.b.c.d

实现类似lodash.get,有以下测试用例:

const object = { 'a': [{ 'b': { 'c': 3 } }] };
 
//=> 3
get(object, 'a[0].b.c');

//=> 3
get(object, 'a[0]["b"]["c"]')

//=> 10086
get(object, 'a[100].b.c', 10086);

问题追问:

1. 如何使用 ts 写法来实现 lodash.get 函数?

shfshanyue avatar Feb 11 '20 12:02 shfshanyue

function lodashGet(obj,exps){ if(typeof exps !== 'string') return obj if(typeof obj !== 'object') return obj let res = obj const arr = exps.split('.') for(let i=0;i<arr.length;i++){ const exp = arr[i] if(res[exp]){ res = res[exp] } else{ return undefined } } return res }

var obj = {test:{arr:[{name:1}]}}

lodashGet(obj,'test.arr.0.name')

miaooow avatar Jan 05 '21 10:01 miaooow

代码见 如何实现类似 lodash.get 函数 - codepen

function get (source, path, defaultValue = undefined) {
  // a[3].b -> a.3.b -> [a, 3, b]
  const paths = path.replace(/\[(\w+)\]/g, '.$1').replace(/\["(\w+)"\]/g, '.$1').replace(/\['(\w+)'\]/g, '.$1').split('.')
  let result = source
  for (const p of paths) {
    result = result?.[p]
  }
  return result === undefined ? defaultValue : result 
}
const object = { a: [{ b: { c: 3 } }] };
const result = _.get(object, 'a[0].b.c', 1); 

shfshanyue avatar Jun 11 '21 12:06 shfshanyue

function getValue(context, path, defaultValue) {
  if (Object.prototype.toString.call(context) !== '[object Object]'
    && Object.prototype.toString.call(context) !== '[object Array]') {
    return context;
  }
  let paths = [];
  if (Array.isArray(path)) {
    paths = [...path];
  } else if (Object.prototype.toString.call(path) === '[object String]') {
    paths = path.replace(/\[/g, '.').replace(/\]/g, '').split('.').filter(Boolean);
  } else {
    paths = [String(path)];
  }
  let result = undefined;
  for (let i = 0; i < paths.length; i++) {
    const key = paths[i];
    result = result ? result[key] : context[key];
    if (result !== null && typeof result !== 'undefined') {
      continue;
    }
    return defaultValue || undefined;
  }
  return result;
}

haotie1990 avatar Jul 28 '21 11:07 haotie1990

// 其实原本是按照lodash实现的 但是这里有个差异是如果属性存在就返回其实没有把目标元素是`undefined`的时候设置回default
function get (arm, params = '', defaultVal) {
    if (typeof params !== 'string' && !Array.isArray(params)) {
        throw new Error(`${params} is not string or array`)
    }
    if (!Array.isArray(params)) {
        params = params.split(/\].|[\[.]/)
    }
    for (let i = 0; i < params.length; i++) {
        if (Object.prototype.hasOwnProperty.call(arm, params[i])) {
            arm = arm[params[i]]
        } else {
            return defaultVal
        }
    }
    return arm
}

function get (obj, keyStr, defVal = undefined) {
    let matchArr = Array.from(keyStr.matchAll(/(\[).*?(\])|(?<=\.).*?(?=\.)|(?<=\.).*?$/g))
    let val = obj
    for (let i = 0; i < matchArr.length; i++) {
        if (typeof val === 'object' && val !== null || typeof val === 'function') {
            let key = matchArr[i][0]
            if (key[0] === '[') {
                key = key.slice(1, key.length - 1)
            }
            val = obj[key]
        } else {
            return defVal
        }
    }
    if (val === undefined) {
        return defVal
    } else {
        return val
    }
}


type strToPoint<S> =
    S extends `${infer F}["${infer M}`  ? strToPoint<`${F}.${M}`> :
        S extends `${infer F}"]${infer M}`  ? strToPoint<`${F}${M}`> :
            S extends `${infer F}['${infer M}`  ? strToPoint<`${F}.${M}`> :
                S extends `${infer F}']${infer M}`  ? strToPoint<`${F}${M}`> :
                    S extends `${infer F}[${infer M}`  ? strToPoint<`${F}.${M}`> :
                        S extends `${infer F}]${infer M}`  ? strToPoint<`${F}${M}`> : S

type strPointToArr<S, A extends string[] = []> =
    S extends `${infer F}.${infer M}`  ? strPointToArr<M, [...A, F]> :
        S extends '' ? A : [...A, S]


type getReturnType<O extends unknown, K extends string[], D extends unknown = undefined> =
    K extends [] ? O extends undefined ? D : O :
        O extends Record<string, any> ? getReturnType<K[0] extends keyof O ? O[K[0]] : undefined, K extends [first:infer F, ...args: infer L] ? L : [] ,D > :
            D

let obj = {
    a: [1, 'lisi', {
        b: {
            c: 4
        },
        f: {
            g: 'wangwu'
        }
    }]
} as const

type get<O extends Record<string, any>, K extends string, Def extends unknown = undefined> =
    (obj: O, keyStr: K, defVal: Def) => getReturnType<O, strPointToArr<strToPoint<K>>, Def>


type zz = get<typeof obj, 'a[2][b].c', '123'>
type zzz = get<typeof obj, 'd[e]', 'defaultVal'>

heretic-G avatar Jul 31 '21 08:07 heretic-G

const lodashGet = (object: { [key: string]: any }, path: Array<string> | string, defaultValue?: any): any => {
    let result: any
    const findArrayPath = (path: Array<string>): any => {
        if (path.length === 0) {
            return result = defaultValue
        }
        result = object
        for (const p of path) {
            if (p in result) {
                result = result[p]
            } else {
                result = defaultValue
                break
            }
        }
        return result
    }
    if (Array.isArray(path)) {
        result = findArrayPath(path)
    } else  {
        path.replace
        let normalizedPath = path.replace(/\.|\[|\]/g, ' ').split(/\s+/)
        result = findArrayPath(normalizedPath)
    }
    return result
}

const object = { 'a': [{ 'b': { 'c': 3 } }] }

console.log(lodashGet(object, 'a[0].b.c'))
console.log(lodashGet(object, ['a', '0', 'b', 'c']))
console.log(lodashGet(object, 'a.b.c', 'default'))

hwb2017 avatar Sep 28 '21 07:09 hwb2017

function get(obj, keys, defaultValue) {
 
  let tempObj = obj;
  let arr = [];
  if (typeof keys === 'string') {
    let key = '';
    let index = 0;

    while (index < keys.length) {
      const k = keys[index];
      if ((['[', '\'', "\"", '.', ']']).includes(k)) {
        if (key.length) {
          arr.push(key);
        }
        key ='';
      } else {
        key  = key + k;
      }
      index = index +1;
    }
   key && arr.push(key);
  } else {
    arr = keys;
  }

  while (arr.length) {
    tempObj = tempObj[arr.shift()]
    if (tempObj === undefined || tempObj === null) {
      return defaultValue;
    }
  }
  return tempObj;

}


yazhouio avatar Sep 30 '21 16:09 yazhouio

function get(obj, oriPath, defaultVal) {
  const paths = oriPath.split(".");
  const keys = [];
  for (const path of paths) {
    keys.push(...pathHandler(path));
  }

  let res = obj;
  for (const key of keys) {
    if (res[key] === undefined || res[key] === null) return defaultVal;
    res = res[key];
  }

  return res;
}

// 将 path 处理为能用的 keys
function pathHandler(path) {
  const res = [];
  path = path.split("");

  let left = 0;
  let right = 0;
  let str = "";

  while (left < path.length && right < path.length) {
    if (path[left] === "[") {
      right = left + 1;
      while (path[right] !== "]") {
        right++;
      }
      temp = path.slice(left + 1, right).join("");

      left = right + 1;
      temp && res.push(temp);
    } else {
      str += path[left++];
    }
  }
  res.unshift(str);

  return res.map((item) => {
    if (item[0] >= "0" && item[0] <= "9") {
      // 转化为整数
      return parseInt(item);
    }
    if (item.indexOf('"') !== -1 || item.indexOf("'") !== -1) {
      // 去除两边的引号
      return item.slice(1, item.length - 1);
    }
    return item;
  });
}

4may-mcx avatar Aug 14 '22 02:08 4may-mcx