【问题标题】:Find property by name in a deep object在深层对象中按名称查找属性
【发布时间】:2013-03-16 13:38:47
【问题描述】:

我有一个巨大的收藏,我正在寻找收藏中某处的关键属性。获取包含该键/索引的所有对象的引用列表或完整路径的可靠方法是什么?如果有帮助,我会使用 jQuery 和 lodash,你可以忘记无限指针递归,这是一个纯 JSON 响应。

fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "d"); 
// [o.c]

fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "e");
// [o.c.d]

fn({ 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd');
// [o.cc,o.cc.dd]

fwiw lodash 有一个 _.find 函数,可以找到两个嵌套深度的嵌套对象,但之后似乎失败了。 (例如http://codepen.io/anon/pen/bnqyh

【问题讨论】:

    标签: javascript jquery lodash


    【解决方案1】:

    应该这样做:

    function fn(obj, key) {
        if (_.has(obj, key)) // or just (key in obj)
            return [obj];
        // elegant:
        return _.flatten(_.map(obj, function(v) {
            return typeof v == "object" ? fn(v, key) : [];
        }), true);
    
        // or efficient:
        var res = [];
        _.forEach(obj, function(v) {
            if (typeof v == "object" && (v = fn(v, key)).length)
                res.push.apply(res, v);
        });
        return res;
    }
    

    【讨论】:

    • 这几乎对我有用。我不得不将第 3 行改为 return [obj[key]];,这样我才能得到一个键值数组,而不是包装对象
    • @ChrisMontgomery:是的,但是 OP 想要“所有包含该密钥的对象”(可能他正在对结果进行pluck()
    • @Bergi 是有道理的。您的解决方案适用于我的所有场景,而来自 Al Jey 的答案在对象内部的数组中存在对象问题。
    • 这里res.push(v)res.push.apply(res, v);有什么区别?
    • @MichaelTrouw:实际上,如果满足if 条件,v 总是一个数组,因为fn 总是返回一个数组。我们在这里使用push.apply 将多个项目一次附加到res 数组 - 就像res = res.concat(v) 一样,但没有创建新数组。
    【解决方案2】:

    纯 JavaScript 解决方案如下所示:

    function findNested(obj, key, memo) {
      var i,
          proto = Object.prototype,
          ts = proto.toString,
          hasOwn = proto.hasOwnProperty.bind(obj);
    
      if ('[object Array]' !== ts.call(memo)) memo = [];
    
      for (i in obj) {
        if (hasOwn(i)) {
          if (i === key) {
            memo.push(obj[i]);
          } else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
            findNested(obj[i], key, memo);
          }
        }
      }
    
      return memo;
    }
    

    这是你如何使用这个函数:

    findNested({'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd');
    

    结果是:

    [{x: 9}, {y: 9}]
    

    【讨论】:

    • 此解决方案适用于嵌套数组,例如findNested({ 'a': [ {'b': {'c': 7}} ]}, 'b') @Bergi's 这样做
    • @ChrisMontgomery,那是因为最初的问题只包含对象并且没有任何数组,我已经更新了示例。
    • @AlJey,对于您的 lodash 示例,我使用了var o = {a: {really: 'long'}, obj: {that: {keeps: 'going'}}},然后使用了findNested(o, 'that'),这给了我RangeError: Maximum call stack size exceeded。不过,第一个作品很棒!
    • 未捕获 RangeError: 超出最大调用堆栈大小
    • 我在window上使用它
    【解决方案3】:

    这将在一个对象数组(干草)中深度搜索一个值(针),然后返回一个包含结果的数组......

    search = function(hay, needle, accumulator) {
      var accumulator = accumulator || [];
      if (typeof hay == 'object') {
        for (var i in hay) {
          search(hay[i], needle, accumulator) == true ? accumulator.push(hay) : 1;
        }
      }
      return new RegExp(needle).test(hay) || accumulator;
    }
    

    【讨论】:

    • @ryanyuyu - 它是可读的,不接近缩小。这是简单的英语,但确实需要相当多的智商才能理解。这是一个美丽而复杂的解决方案,它使大脑达到极限:)
    • @vsync 代码已被编辑。见original revision
    【解决方案4】:

    如果你可以用纯 JS(或结合 lodash)编写一个递归函数,那将是最好的(按性能),但如果你想从你身边跳过递归并想要一个简单易读的代码(根据性能,这可能不是最好的)然后您可以将lodash#cloneDeepWith 用于必须递归遍历对象的任何目的。

    let findValuesDeepByKey = (obj, key, res = []) => (
        _.cloneDeepWith(obj, (v,k) => {k==key && res.push(v)}) && res
    )
    

    因此,您作为_.cloneDeepWith 的第二个参数传递的回调将递归遍历所有key/value 对,您所要做的就是您要对每个对执行的操作。上面的代码只是您的案例的一个例子。这是一个工作示例:

    var object = {
        prop1: 'ABC1',
        prop2: 'ABC2',
        prop3: {
            prop4: 'ABC3',
            prop5Arr: [{
                    prop5: 'XYZ'
                },
                {
                    prop5: 'ABC4'
                },
                {
                    prop6: {
                        prop6NestedArr: [{
                                prop1: 'XYZ Nested Arr'
                            },
                            {
                                propFurtherNested: {key100: '100 Value'}
                            }
                        ]
                    }
                }
            ]
        }
    }
    let findValuesDeepByKey = (obj, key, res = []) => (
        _.cloneDeepWith(obj, (v,k) => {k==key && res.push(v)}) && res
    )
    
    console.log(findValuesDeepByKey(object, 'prop1'));
    console.log(findValuesDeepByKey(object, 'prop5'));
    console.log(findValuesDeepByKey(object, 'key100'));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

    【讨论】:

      【解决方案5】:

      使用 Deepdash 你可以pickDeep 然后从中获取paths,或者indexate(构建路径->值对象)

      var obj = { 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}
      
      var cherry = _.pickDeep(obj,"d");
      
      console.log(JSON.stringify(cherry));
      // {"cc":{"d":{}},"dd":{"d":{}}}
      
      var paths = _.paths(cherry);
      
      console.log(paths);
      // ["cc.d", "dd.d"]
      
      paths = _.paths(cherry,{pathFormat:'array'});
      
      console.log(JSON.stringify(paths));
      // [["cc","d"],["dd","d"]]
      
      var index = _.indexate(cherry);
      
      console.log(JSON.stringify(index));
      // {"cc.d":{},"dd.d":{}}
      

      这是Codepen demo

      【讨论】:

      • 我在尝试搜索窗口对象上的属性时遇到了超出最大调用堆栈的错误。
      • 您是否打开了循环检查选项?默认情况下它是关闭的。分享您的代码,我会帮助修复它。
      • 当我尝试使用 var cherry = _.pickDeep( window, 'Netscape', {checkCircular: true}); 时出现错误:TypeError: Right-hand side of 'instanceof' is not an object
      • 我没有用于测试的 Netscape 浏览器,但这适用于 chrome:(function(){ var ld = window._; window._ = null; var cherry = ld.pickDeep( window, 'Netscape', {checkCircular: true}); console.log({cherry}); })(); 当 deepdash 尝试迭代它所依赖的全局 lodash 对象时会发生一些奇怪的事情,因此将其移动到某个本地范围。
      • 我得到了完全相同的错误。它也每次都会清除我的应用程序的本地存储,这很奇怪。我跑了这个:(function(){ var ld = _; deepdash(ld); _ = null; var cherry = ld.pickDeep( window, 'Netscape', {checkCircular: true}); console.log({cherry}); })();
      【解决方案6】:

      这样的事情会起作用,将其转换为对象并向下递归。

      function find(jsonStr, searchkey) {
          var jsObj = JSON.parse(jsonStr);
          var set = [];
          function fn(obj, key, path) {
              for (var prop in obj) {
                  if (prop === key) {
                      set.push(path + "." + prop);
                  }
                  if (obj[prop]) {
                      fn(obj[prop], key, path + "." + prop);
                  }
              }
              return set;
          }
          fn(jsObj, searchkey, "o");
      }
      

      小提琴:jsfiddle

      【讨论】:

        【解决方案7】:

        如果您没有看到来自@eugene 的更新答案,此调整允许传递要搜索的键列表!

        // Method that will find any "message" in the Apex errors that come back after insert attempts
        // Could be a validation rule, or duplicate record, or pagemessage.. who knows!
        // Use in your next error toast from a wire or imperative catch path!   
        // message: JSON.stringify(this.findNested(error, ['message', 'stackTrace'])),
        // Testing multiple keys: this.findNested({thing: 0, list: [{message: 'm'}, {stackTrace: 'st'}], message: 'm2'}, ['message', 'stackTrace'])
        findNested(obj, keys, memo) {
            let i,
                proto = Object.prototype,
                ts = proto.toString,
                hasOwn = proto.hasOwnProperty.bind(obj);
          
            if ('[object Array]' !== ts.call(memo)) memo = [];
          
            for (i in obj) {
              if (hasOwn(i)) {
                if (keys.includes(i)) {
                  memo.push(obj[i]);
                } else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
                  this.findNested(obj[i], keys, memo);
                }
              }
            }
          
            return memo.length == 0 ? null : memo;
        }
        

        【讨论】:

          【解决方案8】:
          Array.prototype.findpath = function(item,path) {
            return this.find(function(f){return item==eval('f.'+path)});
          }
          

          【讨论】:

          • 您应该避免“仅代码”的答案。请提供有关您的代码的一些背景信息。
          【解决方案9】:

          我是这样做的:

          function _find( obj, field, results )
          {
              var tokens = field.split( '.' );
          
              // if this is an array, recursively call for each row in the array
              if( obj instanceof Array )
              {
                  obj.forEach( function( row )
                  {
                      _find( row, field, results );
                  } );
              }
              else
              {
                  // if obj contains the field
                  if( obj[ tokens[ 0 ] ] !== undefined )
                  {
                      // if we're at the end of the dot path
                      if( tokens.length === 1 )
                      {
                          results.push( obj[ tokens[ 0 ] ] );
                      }
                      else
                      {
                          // keep going down the dot path
                          _find( obj[ tokens[ 0 ] ], field.substr( field.indexOf( '.' ) + 1 ), results );
                      }
                  }
              }
          }
          

          测试它:

          var obj = {
              document: {
                  payload: {
                      items:[
                          {field1: 123},
                          {field1: 456}
                          ]
                  }
              }
          };
          var results = [];
          
          _find(obj.document,'payload.items.field1', results);
          console.log(results);
          

          输出

          [ 123, 456 ]
          

          【讨论】:

            【解决方案10】:

            我们使用object-scan 进行数据处理任务。一旦你了解了如何使用它,它就非常棒了。

            // const objectScan = require('object-scan');
            
            const haystack = { a: { b: { c: 'd' }, e: { f: 'g' } } };
            const r = objectScan(['a.*.*'], { joined: true, rtn: 'entry' })(haystack);
            console.log(r);
            // => [ [ 'a.e.f', 'g' ], [ 'a.b.c', 'd' ] ]
            .as-console-wrapper {max-height: 100% !important; top: 0}
            <script src="https://bundle.run/object-scan@13.8.0"></script>

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

            网站上还有很多例子。

            【讨论】:

              猜你喜欢
              • 2014-02-12
              • 1970-01-01
              • 2021-03-25
              • 2018-04-11
              • 1970-01-01
              • 2016-12-28
              • 2017-08-05
              • 2010-09-25
              • 1970-01-01
              相关资源
              最近更新 更多