JavaScript查找嵌套数组中的子对象

18

我有一个类似下面结构的javascript结构(嵌套的对象数组)

var categoryGroups = [
    {
        Id: 1, Categories: [
            { Id: 1 },
            { Id: 2 }, 
        ]

    },
    {
        Id: 2, Categories: [
            { Id: 100 },
            { Id: 200 },
        ]

    }
]

假设所有分类 Id 都是唯一的,我想要找到一个匹配指定 Id 的子分类对象。

我有下面这段代码,但是想知道是否还有更简洁的方法:

var category, categoryGroup, found = false;
for (i = 0; i < categoryGroups.length ; i++) {
    categoryGroup = categoryGroups[i];
    for (j = 0; j < categoryGroup.Categories.length; j++) {
        category = categoryGroup.Categories[j];
        if (category.Id === id) {
            found = true;
            break;
        }
    }
    if (found) break;
}

1
我认为你的代码有 bug。条件 if (category.Id === id) 应该改为 if (category.Id === categoryGroup.Id)。如果我错了请纠正我。 - Raghu
我建议使用像lodashunderscore这样的库。更具体地说,看看lodash的find方法 - Vahe Khachikyan
假设您想查找ID为100的项目,您希望将输出获取为{ Id: 100 }吗? - thefourtheye
嵌套是否超过两层? - Ja͢ck
10个回答

26

在ES2019中使用 flatMap

const category = categoryGroups.flatMap(cg => cg.Categories).find(c => c.Id === categoryId);

太棒了!我一直在尝试使用find、filter、map等方法,但是我无法从嵌套对象中获取所需的数据,尝试了这个方法后,哇!神奇! - Jimmy Long
完美!这个可以很好地检索嵌套对象中的数据。 - Rakesh

13

注意:这里使用了几个Array.prototype函数,它们只在ECMAScript 5中添加,因此在旧浏览器上不起作用,除非使用polyfill。

您可以循环遍历数组中的所有一级对象,然后根据条件过滤类别并将所有匹配项收集到一个数组中。最终结果将是匹配项数组的第一个元素(如果数组为空,则表示没有匹配项)。

var matches = [];
var needle = 100; // what to look for

arr.forEach(function(e) {
    matches = matches.concat(e.Categories.filter(function(c) {
        return (c.Id === needle);
    }));
});

console.log(matches[0] || "Not found");

JSFiddle: http://jsfiddle.net/b7ktf/1/

References:

Array.prototype.forEach
Array.prototype.concat
Array.prototype.filter


6

仅使用 Array.prototype.filter()

如果您确信要查找的 id 存在,可以执行:

var id = 200; // surely it exists
var category = arr.filter(g => g.Categories.filter(c => c.Id === id)[0])[0].Categories.filter(c => c.Id === id)[0];

如果您不确定它是否存在:

var id = 201; // maybe it doesn't exist
var categoryGroup = arr.filter(e => e.Categories.filter(c => c.Id === id)[0])[0]; 
var category = categoryGroup ? categoryGroup.Categories.filter(c => c.Id === id)[0] : null;

jsfiddle


2

使用 reduce 和递归:

function nestedSearch(value) {
    return categoryGroups.reduce(function f(acc, val) {
        return (val.Id === value) ? val :
            (val.Categories && val.Categories.length) ? val.Categories.reduce(f, acc) : acc;
    });
}

> try on JSFiddle


2
如果你想实际返回内部类别(而不仅仅是检查其是否存在),你可以使用 reduce
return categoryGroups.reduce((prev, curr) => {
    //for each group: if we already found the category, we return that. otherwise we try to find it within this group
    return prev || curr.Categories.find(category => category.Id === id);
}, undefined);

这会在内部类别上进行短路,并且每个类别组只会触摸一次。它也可以修改为在类别组上进行短路。

这里是一个JS Fiddle演示。


聪明!这是最好的“本地”方法,只需要一行缩进。有趣的是,否则需要undefinedinitialValue默认为数组的第一个元素)。 - Cameron Wilby

1

检查fiddle中的代码

var categoryGroups = [
    {
        Id: 1, Categories: [
            { Id: 1 },
            { Id: 2 }, 
        ]

    },
    {
        Id: 2, Categories: [
            { Id: 100 },
            { Id: 200 },
        ]

    }
]
var id = 100;
var x = 'not found';
var category, categoryGroup, found = false;
for (i = 0; i < categoryGroups.length ; i++) {
    categoryGroup = categoryGroups[i];
    for (j = 0; j < categoryGroup.Categories.length; j++) {
        category = categoryGroup.Categories[j];
        if (category.Id == id) {
            var x = category.Id;
            found = true;
            break;
        }
    }
    if (found) break;
}
alert(x);

上面的代码检查数组中是否存在id = 100。如果找到了,将弹出该值,否则弹出未找到。为了演示目的,值“100”已经硬编码。

你能解释一下为什么你将id设置为100吗? - Raghu
@Raghu:不要使用输入,而是将要检查的值硬编码。根据需求,必须将其替换为要检查的值。 - A J
明白了。如果您能创建一个函数使它更清晰,那就太好了。可以像这样:function getCategory(groups, id) - Raghu
@Raghu:谢谢,但我只是想快速回复这篇帖子。 - A J

1
你可以将其包装在一个函数中,以摆脱笨拙的break;语法,并且你可以在for(;;)结构内将每个元素加载到变量中,以减少一些代码行数。
function subCategoryExists(groups, id)
{
  for (var i = 0, group; group = groups[i]; ++i) {
    for (var k = 0, category; category = group.Categories[k]; ++k) {
      if (category.Id == id) {
        return true;
      }
    }
  }
  return false;
}

var found = subCategoryExists(categoryGroups, 100);

1
这段代码在嵌套多个级别后将无法正常工作,因此它不具有可扩展性。 - Sahil Babbar
1
@SahilBabbar 好的,但是为什么你只在这个答案上留下了那个评论呢? - Ja͢ck
@AnzilkhaN 没错,如果你想遍历未知深度,则可能需要递归的方法。 - Ja͢ck

1
使用NodeJS的lodash库是一个简单的方法(假设您正在使用NodeJS):
const _ = require('lodash');
let category ;
let categoryGroup = _.find(categoryGroups, (element)=>{
  category = _.find(element.Categories, {Id : 100});
  return category;
});

console.log(categoryGroup); // The category group which has the sub category you are looking for
console.log(category); // The exact category you are looking for

0

你可以使用underscore

var cat = _(categoryGroups).
  chain().
  pluck('Categories').
  flatten().
  findWhere({Id: 2}).
  value();

我在这里做的是将所有类别值提取到一个单独的数组中,然后搜索正确的类别。

编辑:抱歉,第一次没有正确理解你的问题。正如评论所建议的那样,你可能不想仅仅为此使用下划线,但这是我会这样做的方法 :)


我认为他想通过类别ID进行查找。 - Mulan
如果我的项目不想使用下划线怎么办?他的问题很明确,他希望在原生JavaScript中进行优化。 - Raghu
@Raghu,是的,除非您已经在使用它,否则我不会为这个任务添加下划线。 - Mulan
也许下划线不适合你的项目,但对于那些不介意加下划线的人来说,这是一个很好的选择。 - Ayush
不是,但它提供了足够的信息,使任何熟悉JS的人可以将其扩展到更深层次。 - Ayush

0
我们现在正在使用object-scan进行数据处理。一旦你理解了它,它就非常强大。对于你的问题,它会是这样的:

// const objectScan = require('object-scan');

const lookup = (id, data) => objectScan(['Categories.Id'], {
  useArraySelector: false,
  abort: true,
  rtn: 'parent',
  filterFn: ({ value }) => value === id
})(data);

const categoryGroups = [{ Id: 1, Categories: [{ Id: 1 }, { Id: 2 }] }, { Id: 2, Categories: [{ Id: 100 }, { Id: 200 }] }];

console.log(lookup(1, categoryGroups));
// => { Id: 1 }
console.log(lookup(100, categoryGroups));
// => { Id: 100 }
console.log(lookup(999, categoryGroups));
// => undefined
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>

声明: 我是object-scan的作者


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