【问题标题】:How to find the key of a value in a nested object recursively如何递归地在嵌套对象中查找值的键
【发布时间】:2019-08-27 14:06:24
【问题描述】:

我想通过递归在 Javascript 嵌套对象中找到值的键。

这是我对该功能的尝试。有没有更优雅的方法来实现这一点?

const foo = { data: { data2: { data3: 'worked' }, data21: 'rand' }, data01: 'rand01' }

function findKey(obj, target) {
  let result = null;
  if (_.isEmpty(obj) || !_.isObject(obj)){
    return null;
  }
  if (!_.isArray(obj) && Object.keys(obj).length > 0) {
    for(let i=0; i < Object.keys(obj).length; i++){
      let key = Object.keys(obj)[i];
      let val = obj[key];
      if (val === target) {
        return key;
      }else{
        result = findKey(val, target);
      }
      if (result) {break}
    }
  }
  return result;
}
console.log(findKey(foo, 'worked'))
&lt;script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"&gt;&lt;/script&gt;

例如,有没有办法避免检查result 的值然后中断? 我觉得 result 应该能够在调用堆栈中冒泡,直到它在第一个函数调用处返回而不必中断。

【问题讨论】:

  • 如果有多个值怎么办?
  • @briosheje 我只需要第一个。
  • 数据集是否一致?我们可能会遇到数组或输入是数组吗?
  • @briosheje 我们可能会遇到数组,但输入始终是一个对象。
  • 您可能希望看到这个问题及其答案,以解决更广泛的问题,即找到一个值的整个匹配路径(例如,上面的 ['data1', 'data2', 'data3']可能加入'data1.data2.data3'):stackoverflow.com/q/56066101

标签: javascript arrays object recursion


【解决方案1】:

您可以使用Object.entries 来迭代所有键。

另外值得注意的是,Object.entries 也适用于 Array,所以没有 需要特殊处理。

const foo = { data: { data2: { data3: 'worked' }, data21: 'rand' }, data01: 'rand01', arr: [{arrtest: "arr"},'xyz']}

function findKey(obj, target) {
  const fnd = obj => {
    for (const [k, v] of Object.entries(obj)) {
      if (v === target) return k;
      if (typeof v === 'object') {
        const f = fnd(v);
        if (f) return f;
      }
    }
  }
  return fnd(obj);
}
console.log(findKey(foo, 'worked'))
console.log(findKey(foo, 'arr'))
console.log(findKey(foo, 'xyz'))

【讨论】:

    【解决方案2】:

    这是最近恢复的,但没有提到一种有用的技术,生成器函数。通常,它们简化了需要提前停止的递归遍历。在这里,我们将问题分解为两个函数。一、生成器函数nestedEntries获取对象中所有(嵌套的)键值对。另一个调用它并返回与提供的目标值匹配的第一个。

    function* nestedEntries(obj) {
      for (let [k, v] of Object.entries(obj)) {
        yield [k, v]
        if (Object(v) === v) {
          yield* nestedEntries(v)
        }
      }
    }
    
    const findKey = (obj, target) => {
      for (let [k, v] of nestedEntries(obj)) {
        if (v === target) return k
      }
      return null
    }
    
    const foo = { data01: 'rand01', data: { data21: 'rand', data2: { data3: 'worked' } } };
    
    console.log(findKey(foo, 'worked'))

    【讨论】:

      【解决方案3】:

      在上面提出的几个问题之后,函数看起来应该:

      • 假设输入始终是一个对象。
      • 假设它可能在途中遇到数组。
      • 假设它必须在满足一个值后停止(如果存在多个值)。

      OP 提供的输入代码不处理数组情况。

      以下代码示例用于处理这些示例案例:

      • 简单的嵌套对象结构。
      • 具有嵌套对象或元素数组的对象。

      下面的函数接受第二个参数,它是一个回调来评估遇到的元素是否真的是我们正在寻找的那个。这样,更容易处理更复杂的检查。

      保留递归方法,一旦满足关键,该函数只需 return 以避免不必要的搜索。

      const foo = { data: { data2: { data3: 'worked' }, data21: 'rand' }, data01: 'rand01' };
      const fooWithArrays = {
        data: {
          data2: {
            data3: 'not here'
          },
          data4: [
            { data5: 'worked' },
            { data6: 'not me' }
          ]
        }
      };
      const fooWithExpression = {
        data: {
         data2: {
          data3: { id: 15, name: 'find me!' }
         },
         data21: {
          data25: 'not me'
         }
        }
      };
      
      const findKeyByValue = (obj, equalsExpression) => {
        // Loop key->value pairs of the input object.
        for (var [key, v] of Object.entries(obj)) {
          // if the value is an array..
          if (Array.isArray(v)) {
            // Loop the array.
            for (let i = 0; i < v.length; i++) {
              // check whether the recursive call returns a result for the nested element.
              let res = findKeyByValue(v[i], equalsExpression);
              // if so, the key was returned. Simply return.
              if (res !== null && res !== undefined) return res;
            }
          }
          // otherwise..
          else {
            // if the value is not null and not undefined.
            if (v !== null && v !== undefined) {
              // if the value is an object (typeof(null) would give object, hence the above if). 
              if (typeof(v) === 'object') {
                // check whether the value searched is an object and the match is met.
                if (equalsExpression(v)) return key;
                // if not, recursively keep searching in the object.
                let res = findKeyByValue(v, equalsExpression);
                // if the key is found, return it.
                if (res !== null && res !== undefined) return res;
              }
              else {
                // finally, value must be a primitive or something similar. Compare.
                let res = equalsExpression(v);
                // if the condition is met, return the key.
                if (res) return key;
                // else.. continue.
              }
            }
            else continue;
          }
        }
      }
      
      console.log( findKeyByValue(foo, (found) => found === 'worked') );
      console.log( findKeyByValue(fooWithArrays, (found) => found === 'worked') );
      console.log( findKeyByValue(fooWithExpression, (found) => found && found.id && found.id === 15) );

      【讨论】:

        【解决方案4】:

        如果obj 恰好是一个带有子对象但没有数组的普通对象,那么这就可以解决问题。

        function findKey(obj, target) {
          for (let key in obj) {
            const val = obj[key];
            if (val === target) {
              return key;
            }
            if (typeof val === "object" && !Array.isArray(val)) {
              const ret = findKey(val, target);
              if (ret) return ret;
            }
          }
        }
        const foo = {
          data: { data2: { data3: "worked" }, data21: "rand" },
          data01: "rand01",
        };
        
        console.log(findKey(foo, "worked"));
        console.log(findKey(foo, "bloop"));

        【讨论】:

          【解决方案5】:

          如果数据只是没有数组的对象,您可以尝试正则表达式:

          const foo = { data: { data2: { data3: 'worked' }, data21: 'rand' }, data01: 'rand01' }
          const out = JSON.stringify(foo).match(/"([^{}]+)":"worked"/)[1];
          console.log(out);

          【讨论】:

            【解决方案6】:

            对于像这样的简单数据处理任务,我们使用object-scan。一旦你把头绕在它周围,它就会非常强大,让事情变得更干净。以下是您解决问题的方法

            (冒昧地从@briosheje 回答中获取输入数据)

            // const objectScan = require('object-scan');
            
            const findKeyByValue = (data, fn) => objectScan(['**'], {
              abort: true,
              rtn: 'property',
              filterFn: ({ value }) => fn(value) === true
            })(data);
            
            const foo = { data: { data2: { data3: 'worked' }, data21: 'rand' }, data01: 'rand01' };
            const fooWithArrays = { data: { data2: { data3: 'not here' }, data4: [{ data5: 'worked' }, { data6: 'not me' }] } };
            const fooWithExpression = { data: { data2: { data3: { id: 15, name: 'find me!' } }, data21: { data25: 'not me' } } };
            
            console.log(findKeyByValue(foo, (found) => found === 'worked'));
            // => data3
            console.log(findKeyByValue(fooWithArrays, (found) => found === 'worked'));
            // => data5
            console.log(findKeyByValue(fooWithExpression, (found) => found && found.id && found.id === 15));
            // => data3
            console.log(findKeyByValue(fooWithExpression, (found) => false));
            // => undefined
            .as-console-wrapper {max-height: 100% !important; top: 0}
            &lt;script src="https://bundle.run/object-scan@13.8.0"&gt;&lt;/script&gt;

            免责声明:我是object-scan的作者

            【讨论】:

              猜你喜欢
              • 2016-10-24
              • 1970-01-01
              • 2018-02-14
              • 1970-01-01
              • 2019-04-22
              • 2019-03-02
              • 2018-01-30
              • 2012-02-12
              • 2022-11-24
              相关资源
              最近更新 更多