JSON 对象中的 JavaScript 递归搜索

IT技术 javascript json recursion
2021-02-15 15:24:07

我试图返回一个 JSON 对象结构中的特定节点,如下所示

{
    "id":"0",
    "children":[
        {
            "id":"1",
            "children":[...]
        },
        {
            "id":"2",
            "children":[...]
        }
    ]
}

所以这是一个树状的子父关系。每个节点都有一个唯一的 ID。我试图找到一个特定节点这样

function findNode(id, currentNode) {

    if (id == currentNode.id) {
        return currentNode;
    } else {
        currentNode.children.forEach(function (currentChild) {            
            findNode(id, currentChild);
        });
    }
}  

我执行搜索,例如findNode("10", rootNode)但即使搜索找到匹配项,该函数也始终返回undefined我有一种不好的感觉,即递归函数在找到匹配项后不会停止并继续运行 finally 返回,undefined因为在后面的递归执行中它没有到达返回点,但我不知道如何解决这个问题。

请帮忙!

6个回答

递归搜索时,您必须通过返回结果将结果传回。但是,您没有返回 的结果findNode(id, currentChild)

function findNode(id, currentNode) {
    var i,
        currentChild,
        result;

    if (id == currentNode.id) {
        return currentNode;
    } else {

        // Use a for loop instead of forEach to avoid nested functions
        // Otherwise "return" will not work properly
        for (i = 0; i < currentNode.children.length; i += 1) {
            currentChild = currentNode.children[i];

            // Search in the current child
            result = findNode(id, currentChild);

            // Return the result if the node has been found
            if (result !== false) {
                return result;
            }
        }

        // The node has not been found and we have no more options
        return false;
    }
}
我现在正在premierepro扩展脚本的所有垃圾箱中按名字搜索一个孩子:)
2021-04-17 15:24:07
我不得不向 for 循环添加额外的检查(即 for (i = 0; currentNode.children !== undefined && i < currentNode.children.length; i += 1))以避免错误“TypeError:Cannot在“孩子”不存在的叶节点上读取属性“长度”未定义”。
2021-04-21 15:24:07
中断无法找到未定义的长度。它是一个文档对象。
2021-04-23 15:24:07
谢谢!工作完美。
2021-05-05 15:24:07
这是一个类似的解决方案,但代码较少stackoverflow.com/a/52205610/3666971
2021-05-05 15:24:07
function findNode(id, currentNode) {

    if (id == currentNode.id) {
        return currentNode;
    } else {
        var result;
        currentNode.children.forEach(function(node){
            if(node.id == id){
                result = node;
                return;
            }
        });
        return (result ? result : "No Node Found");
    }
}
console.log(findNode("10", node));

如果该节点存在于节点列表中,则此方法将返回该节点。但这将遍历节点的所有子节点,因为我们无法成功中断forEach流程。更好的实现如下所示。

function findNode(id, currentNode) {

    if (id == currentNode.id) {
        return currentNode;
    } else {
        for(var index in currentNode.children){
            var node = currentNode.children[index];
            if(node.id == id)
                return node;
            findNode(id, node);
        }
        return "No Node Present";
    }
}
console.log(findNode("1", node));
@Mehr88sh 抱歉,已改正!!
2021-04-28 15:24:07
亲爱的@Triode,您定义result但从不使用它。
2021-05-12 15:24:07

我使用以下

var searchObject = function (object, matchCallback, currentPath, result, searched) {
    currentPath = currentPath || '';
    result = result || [];
    searched = searched || [];
    if (searched.indexOf(object) !== -1 && object === Object(object)) {
        return;
    }
    searched.push(object);
    if (matchCallback(object)) {
        result.push({path: currentPath, value: object});
    }
    try {
        if (object === Object(object)) {
            for (var property in object) {
                if (property.indexOf("$") !== 0) {
                    //if (Object.prototype.hasOwnProperty.call(object, property)) {
                        searchObject(object[property], matchCallback, currentPath + "." + property, result, searched);
                    //}
                }
            }
        }
    }
    catch (e) {
        console.log(object);
        throw e;
    }
    return result;
}

然后你可以写

searchObject(rootNode, function (value) { return value != null && value != undefined && value.id == '10'; });

现在这适用于循环引用,您可以通过更改matchCallback函数来匹配您喜欢的任何字段或字段组合

匹配回调是一个好主意,我也喜欢你得到路径和对象。干杯!
2021-04-17 15:24:07
@atfede 我返回的原因result是允许多击一次。我有点不确定你的意思if (property.indexOf("NameOfTheNodeYouAreLookingFor") !== 0) {}
2021-04-26 15:24:07
它有效,谢谢。您应该提到,为了搜索特定节点,您应该更改此部分: if (property.indexOf("NameOfTheNodeYouAreLookingFor") !== 0) {} 并且还更改为返回对象;而不是返回结果;
2021-05-12 15:24:07

由于这个老问题已被重新提出,这里有一种不同的方法。我们可以编写一个相当通用的searchTree函数,然后在findId函数中使用它searchTree做遍历对象的工作;它接受回调以及树;回调确定节点是否匹配。除了节点之外,回调还提供了两个函数nextfound,我们不带参数调用它们,分别表示我们应该继续或找到匹配项。如果未找到匹配项,则返回null

它看起来像这样:

const searchTree = (fn) => (obj) =>
  Array.isArray(obj)
    ? obj.length == 0
      ? null
      : searchTree (fn) (obj [0]) || searchTree (fn) (obj .slice (1))
    : fn (
      obj,
      () => searchTree (fn) (obj .children || []),
      () => obj
    )

const findId = (target, obj) => searchTree (
  (node, next, found) => node.id == target ? found () : next(),
) (tree)


const tree = {id: 1, name: 'foo', children: [
  {id: 2, name: 'bar', children: []}, 
  {id: 3, name: 'baz', children: [
    {id: 17, name: 'qux', children: []}, 
    {id: 42, name: 'corge', children: []},
    {id: 99, name: 'grault', children: []}
  ]}
]}


console .log (findId (42, tree))
console .log (findId (57, tree))

此代码特定于在属性下的数组中找到子节点的结构children虽然我们可以根据需要使其更通用,但我发现这是一个可以支持的通用结构。

有一个很好的论据认为,用相互递归来编写会更好。如果我们愿意,我们可以获得与此版本相同的 API:

const searchArray = (fn) => ([x, ...xs]) =>
  x === undefined
    ? null
    : searchTree (fn) (x) || searchArray (fn) (xs)

const searchTree = (fn) => (obj) =>
  fn (
    obj,
    () => searchArray (fn) (obj .children || []),
    (x) => x
  )

这以相同的方式工作。但我发现代码更清晰。不过,两者都应该完成这项工作。

我们使用对象扫描来满足我们的数据处理需求。它在概念上非常简单,但允许很多很酷的东西。这是您解决问题的方法

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

const findNode = (id, input) => objectScan(['**'], {
  abort: true,
  rtn: 'value',
  filterFn: ({ value }) => value.id === id
})(input);

const data = { id: '0', children: [{ id: '1', children: [ { id: '3', children: [] }, { id: '4', children: [] } ] }, { id: '2', children: [ { id: '5', children: [] }, { id: '6', children: [] } ] }] };

console.log(findNode('6', data));
// => { id: '6', children: [] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>

免责声明:我是对象扫描的作者

这里的其他几个答案提到它们适用于圆形对象。对象扫描是否考虑到这一点?
2021-04-23 15:24:07
我确实喜欢简单的对象扫描如何解决这个问题。我还不确定我想在我自己的代码中尝试它,不过我可能很快就会到达那里。
2021-04-25 15:24:07
是的。但是,由于这是性能权衡,因此您需要明确处理它。即你可以简单地添加breakFn: ({ isCircular }) => isCircular
2021-05-04 15:24:07