在嵌套数组中深度查找关键字

125

假设我有一个对象:

[
    {
        'title': "some title"
        'channel_id':'123we'
        'options': [
                    {
                'channel_id':'abc'
                'image':'http://asdasd.com/all-inclusive-block-img.jpg'
                'title':'All-Inclusive'
                'options':[
                    {
                        'channel_id':'dsa2'
                        'title':'Some Recommends'
                        'options':[
                            {
                                'image':'http://www.asdasd.com'                                 'title':'Sandals'
                                'id':'1'
                                'content':{
                                     ...

我想找到id为1的那个对象。是否有类似的函数可以实现这个目的?我可以使用Underscore的_.filter方法,但我需要从头开始过滤。

22个回答

107

递归是你的朋友。我更新了函数以考虑属性数组:

function getObject(theObject) {
    var result = null;
    if(theObject instanceof Array) {
        for(var i = 0; i < theObject.length; i++) {
            result = getObject(theObject[i]);
            if (result) {
                break;
            }   
        }
    }
    else
    {
        for(var prop in theObject) {
            console.log(prop + ': ' + theObject[prop]);
            if(prop == 'id') {
                if(theObject[prop] == 1) {
                    return theObject;
                }
            }
            if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
                result = getObject(theObject[prop]);
                if (result) {
                    break;
                }
            } 
        }
    }
    return result;
}

更新了jsFiddle:http://jsfiddle.net/FM3qu/7/


1
但是等一下,看看我的代码,选项可能有很多个对象[{}, {}]。这会怎么运作? - Harry
1
如果您在选项中有多个对象,则此方法无法正常工作。 - Harry
1
已修复。我添加了结果变量和else逻辑。更新后的jsFiddle显示它正在工作。 - Zach
1
Zack,我稍微修改了你的代码,现在它运行得很好。非常感谢!看起来我现在无法访问jsfiddle。一旦它能够正常工作,我会分享我的更改。 - Harry
如果是这种情况,那么在返回后添加一个检查以查看它是否为空,然后退出函数。 - Zach
显示剩余6条评论

59

另一个(有点儿傻)的选项是利用 JSON.stringify 的自然递归特性,并在字符串化过程中传递一个替换函数,该函数在每个嵌套对象上运行:

const input = [{
  'title': "some title",
  'channel_id': '123we',
  'options': [{
    'channel_id': 'abc',
    'image': 'http://asdasd.com/all-inclusive-block-img.jpg',
    'title': 'All-Inclusive',
    'options': [{
      'channel_id': 'dsa2',
      'title': 'Some Recommends',
      'options': [{
        'image': 'http://www.asdasd.com',
        'title': 'Sandals',
        'id': '1',
        'content': {}
      }]
    }]
  }]
}];

console.log(findNestedObj(input, 'id', '1'));

function findNestedObj(entireObj, keyToFind, valToFind) {
  let foundObj;
  JSON.stringify(entireObj, (_, nestedValue) => {
    if (nestedValue && nestedValue[keyToFind] === valToFind) {
      foundObj = nestedValue;
    }
    return nestedValue;
  });
  return foundObj;
};


2
真的非常聪明。 - Claudiu
5
我喜欢你的想法 :) 我只是认为,如果我们已经找到了结果,中断替换函数的递归运行会使它看起来更好。例如,我们可以使用 throw。 https://jsfiddle.net/w4vs36hm/ - Max Martynov
1
这应该是被选中的解决方案!不错! - Cédric Bloem
1
@Bozhinovski 将 let foundObj; 更改为 const foundObjs = [];,并且不要重新分配变量,而是将其推送到数组中。 - CertainPerformance
2
天才!太棒了,适用于很多不同的用例。 - sbonkosky
显示剩余3条评论

45
我应该写:

对我有效的是这种懒惰的方法,不是算法上的懒惰;)

if( JSON.stringify(object_name).indexOf("key_name") > -1 ) {
    console.log("Key Found");
}
else{
    console.log("Key not Found");
}

1
你认为使用以下代码替代如何:if( JSON.stringify(object_name).indexOf('{"key_name":') > -1 ) {,包括JSON.stringify添加的左花括号、双引号和冒号?我认为这可能是一个不错的双重检查,以确保key_name实际上是一个键而不是在“value”部分中。 - edwardsmarkf
5
可能很好,但无法通过在值中包含 key_name 的测试。同时也没有返回包含 id 的对象。 - Ranga
@Ranga...同意,这个解决方案仅适用于您只需检查键是否存在于对象中的情况,前提是它没有在一个值中具有key_name。虽然,如果我们必须确保它仅存在于键中(再次,带有“”周围的key_name不应存在于值中),我们可以修改代码以在indexOf函数内包含“”周围的键名。 - abhinav1602
4
我喜欢它!JSON.stringify(object).includes(text) - Marcelo Pereira Rodrigues
这个代码片段可以判断特定的键是否存在,但是 OP 想要知道如何获取 id 属性值为 1 的对象。这个答案可以被用于 https://dev59.com/5HE85IYBdhLWcg3wwWMb(除了这里匹配 JSON 中任何位置包含搜索字符串,而不是检查是否有该键)。 - Heretic Monkey

23

如果你想在搜索对象时获取id为1的第一个元素,你可以使用这个函数:

function customFilter(object){
    if(object.hasOwnProperty('id') && object["id"] == 1)
        return object;

    for(var i=0; i<Object.keys(object).length; i++){
        if(typeof object[Object.keys(object)[i]] == "object"){
            var o = customFilter(object[Object.keys(object)[i]]);
            if(o != null)
                return o;
        }
    }

    return null;
}
如果您想获取所有id为1的元素,那么(所有id为1的元素将会存储在结果中,正如您所看到的):
function customFilter(object, result){
    if(object.hasOwnProperty('id') && object.id == 1)
        result.push(object);

    for(var i=0; i<Object.keys(object).length; i++){
        if(typeof object[Object.keys(object)[i]] == "object"){
            customFilter(object[Object.keys(object)[i]], result);
        }
    }
}

1
你的解决方案比@regularmike提出的更好,因为我可以通过添加键和值搜索来定制它。它在你的上面运行,但在他的上面不行。 http://jsfiddle.net/76fp54xt/ 工作示例 - Iulian Pinzaru

17

改进了@haitaka的答案,使用关键字和谓词

function  deepSearch (object, key, predicate) {
    if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) return object

    for (let i = 0; i < Object.keys(object).length; i++) {
      let value = object[Object.keys(object)[i]];
      if (typeof value === "object" && value != null) {
        let o = deepSearch(object[Object.keys(object)[i]], key, predicate)
        if (o != null) return o
      }
    }
    return null
}

因此,可以这样调用:

var result = deepSearch(myObject, 'id', (k, v) => v === 1);
或者
var result = deepSearch(myObject, 'title', (k, v) => v === 'Some Recommends');

这是演示:http://jsfiddle.net/a21dx6c0/

已编辑

以相同的方式,您可以找到多个对象。

function deepSearchItems(object, key, predicate) {
        let ret = [];
        if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) {
            ret = [...ret, object];
        }
        if (Object.keys(object).length) {
            for (let i = 0; i < Object.keys(object).length; i++) {
                let value = object[Object.keys(object)[i]];
                if (typeof value === "object" && value != null) {
                    let o = this.deepSearchItems(object[Object.keys(object)[i]], key, predicate);
                    if (o != null && o instanceof Array) {
                        ret = [...ret, ...o];
                    }
                }
            }
        }
        return ret;
    }

2
注意:typeof null === 'object',因此如果对象中的任何值为null,则在尝试递归时会导致函数崩溃。因此,在递归之前,您还应该检查真实性。 - Rafi
@Rafi 谢谢你提到了这个问题,我已经编辑了答案。 - Iulian Pinzaru
键和谓词对我的使用非常有帮助。像魔法一样运作良好。 - Saran Raj

11
如果你对ES6感兴趣,可以使用下列代码:
const findByKey = (obj, kee) => {
    if (kee in obj) return obj[kee];
    for(n of Object.values(obj).filter(Boolean).filter(v => typeof v === 'object')) {
        let found = findByKey(n, kee)
        if (found) return found
    }
}

const findByProperty = (obj, predicate) => {
    if (predicate(obj)) return obj
    for(n of Object.values(obj).filter(Boolean).filter(v => typeof v === 'object')) {
        let found = findByProperty(n, predicate)
        if (found) return found
    }
}

按值查找会有一些不同。

let findByValue = (o, val) => {
    if (o === val) return o;
    if (o === NaN || o === Infinity || !o || typeof o !== 'object') return;
    if (Object.values(o).includes(val)) return o;
    for (n of Object.values(o)) {
        const found = findByValue(n, val)
        if (found) return n
    }
}

然后它们可以这样使用

const arry = [{ foo: 0 }, null, { bar: [{ baz: { nutherKey: undefined, needle: "gotcha!" } }]}]
const obj = { alice: Infinity, bob: NaN, charlie: "string", david: true, ebert: arry }

findByKey(obj, 'needle')
// 'gotcha!'

findByProperty(obj, val => val.needle === 'gotcha!')
// { nutherKey: undefined, needle: "gotcha!" }

findByValue(obj, 'gotcha!')
// { nutherKey: undefined, needle: "gotcha!" }

8

通过谷歌搜索相似功能,我找到了这个页面。基于Zach和regularmike提供的工作,我创建了另一个版本以适应我的需求。
顺便说一句,Zah和regularmike的工作非常棒! 我将在此处发布代码:

function findObjects(obj, targetProp, targetValue, finalResults) {

  function getObject(theObject) {
    let result = null;
    if (theObject instanceof Array) {
      for (let i = 0; i < theObject.length; i++) {
        getObject(theObject[i]);
      }
    }
    else {
      for (let prop in theObject) {
        if(theObject.hasOwnProperty(prop)){
          console.log(prop + ': ' + theObject[prop]);
          if (prop === targetProp) {
            console.log('--found id');
            if (theObject[prop] === targetValue) {
              console.log('----found porop', prop, ', ', theObject[prop]);
              finalResults.push(theObject);
            }
          }
          if (theObject[prop] instanceof Object || theObject[prop] instanceof Array){
            getObject(theObject[prop]);
          }
        }
      }
    }
  }

  getObject(obj);

}

它的功能是在obj内查找任何具有属性名称和值与targetProptargetValue匹配的对象,并将其推送到finalResults数组中。这是用于操作的jsfiddle链接: https://jsfiddle.net/alexQch/5u6q2ybc/

3
可以通过return终极结果数组来进一步改进,而不是接受另一个变量作为输入进行编写。 - RozzA

7
我已经为此创建了一个库:https://github.com/dominik791/obj-traverse
您可以像这样使用findFirst()方法:
var foundObject = findFirst(rootObject, 'options', { 'id': '1' });

现在,foundObject变量存储了您要查找的对象的引用。


6

下面是另一种递归解决方案,适用于数组/列表和对象,或两者的混合:

function deepSearchByKey(object, originalKey, matches = []) {

    if(object != null) {
        if(Array.isArray(object)) {
            for(let arrayItem of object) {
                deepSearchByKey(arrayItem, originalKey, matches);
            }
        } else if(typeof object == 'object') {

            for(let key of Object.keys(object)) {
                if(key == originalKey) {
                    matches.push(object);
                } else {
                    deepSearchByKey(object[key], originalKey, matches);
                }

            }

        }
    }


    return matches;
}

使用方法:

let result = deepSearchByKey(arrayOrObject, 'key'); // returns an array with the objects containing the key

5

您可以在递归函数中使用 JavaScript some 函数。使用 some 的优点是一旦找到子元素就会停止循环。请不要使用 map,在处理大量数据时速度会变慢。

const findChild = (array, id) => {
  let result;
  array.some(
    (child) =>
      (child.id === id && (result = child)) ||
      (result = findChild(child.options || [], id))
  );
  return result;
};

findChild(array, 1)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接