首页 > 解决方案 > 查找嵌套对象中所有匹配键值的路径

问题描述

我有一个函数可以让我找到第一次找到键和值匹配的嵌套对象的路径。

function getPath(obj, givenKey, givenValue) {
    for(var key in obj) {                                   
        if(obj[key] && typeof obj[key] === "object") {      
            var result = getPath(obj[key], givenValue, givenKey);
            if(result) {                                    
                result.unshift(key)
                return result;                              
            }
        } else if(obj[key] === givenValue && key === givenKey ) {
            return [key];
        }   
    }
}

样本数据

var myObj = [
{
    "name": "needle",
    "children": [
    {
        "name": "group2",
        "children": [
        {
            "name": "item0"
        }]
    }]
},
{
    "name": "item1"
},
{
    "name": "needleGroup",
    "children": [
    {
        "name": "needleNestedGroup",
        "children": [
        {
            "name": "item3"
        },
        {
            "name": "needleNestedDeeperGroup",
            "children": [
            {
                "name": "needle"
            }]
        }]
    }]
}];

预期产出

getPath(myObj, "name", "needle"):
      [0, "name"]
      ["2","children","0","children","1","children","0","name"]

但是,我现在有一个多次包含这些键值的对象,所以我有多个匹配项。

我怎样才能将它们全部放在一个数组中?我当前的功能在找到第一个匹配项后才停止。事实上,它是递归的,这让我的事情变得非常复杂

标签: javascriptrecursion

解决方案


我将把它写在一个更通用的findAllPaths函数之上,该函数接受一个谓词并找到对象中与该谓词匹配的所有节点的路径。有了它,那么findPathsByName就很简单了(target) => findAllPaths (({name}) => name == target)

反过来,我建立findAllPaths在我一直使用的pathEntries变体之上。此函数将对象转换为路径/值对数组。有些版本只生成叶子节点。这个为所有节点生成它,包括根(有一个空路径)。这个函数的基本思想是变成这样的:

{a: 'foo', b: {c: ['bar', 'baz'], f: 'qux'}}

进入这个:

[
  [[], {a: 'foo', b: {c: ['bar', 'baz'], f: 'qux'}}], 
  [['a'], 'foo'], 
  [['b'], {c: ['bar', 'baz'], f: 'qux'}], 
  [['b', 'c'], ['bar', 'baz']], 
  [['b', 'c', 0], 'bar'], 
  [['b', 'c', 1], 'baz'], 
  [['b', 'f'], 'qux']
]

其中每个子数组中的第一项是路径,第二项是对该路径的值的引用。

下面是它的样子:

const pathEntries = (obj) => [
  [[], obj],
  ...Object (obj) === obj
    ? Object .entries (obj) .flatMap (
        ([k, x]) => pathEntries (x) .map (
          ([p, v]) => [[Array .isArray (obj) ? Number (k) : k, ... p], v]
        )
      )
    : []
]

const findAllPaths = (predicate) => (o) =>
  [...pathEntries (o)] .filter (([p, v]) => predicate (v, p)) .map (([p]) => p)

const findPathsByName = (target) => findAllPaths (({name}) => name == target)

const myObj = [{name: "needle", children: [{name: "group2", children: [{name: "item0"}]}]}, {name: "item1"}, {name: "needleGroup", children: [{name: "needleNestedGroup", children: [{name: "item3"}, {name: "needleNestedDeeperGroup", children: [{name: "needle"}]}]}]}]

console .log (findPathsByName ('needle') (myObj))
.as-console-wrapper {max-height: 100% !important; top: 0}

该问题要求数组索引的字符串值。我自己更喜欢这里的整数值,但是你稍微简化了函数:

-          ([p, v]) => [[Array .isArray (obj) ? Number (k) : k, ... p], v]
+          ([p, v]) => [[k, ... p], v]

推荐阅读