JSON对象中的JavaScript递归搜索

时间:2014-03-06 11:06:01

标签: javascript json recursion

我正在尝试返回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时感觉不好,因为在后面的递归执行中它没有到达返回点,但我不知道如何解决这个问题。

请帮忙!

7 个答案:

答案 0 :(得分:34)

递归搜索时,必须通过返回结果传回结果。但是,您没有返回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;
    }
}

答案 1 :(得分:4)

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 {
        var result;
        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));

答案 2 :(得分:3)

我使用以下

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函数来匹配您喜欢的任何字段或字段组合。

答案 3 :(得分:1)

既然这个老问题又被提出来了,这里有一个不同的方法。我们可以编写一个相当通用的 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
  )

这也是一样的。但我发现代码更清晰。不过,两者都应该完成这项工作。

答案 4 :(得分:0)

我真的很喜欢树搜索!对于当今大多数复杂的结构化任务,树是极为常见的数据结构。所以我也有类似的午餐任务。我什至进行了深入研究,但还没有发现任何新东西!所以我今天为您准备的是“我如何用现代JS语法实现它”:

// helper
find_subid = (id, childArray) => {
    for( child of childArray ) {
        foundChild = find_id( i, child ); // not sub_id, but do a check (root/full search)!
        if( foundChild ) // 200 
            return foundChild;
    }
    return null; // 404
}

// actual search method
find_id = (id, parent) => (id == parent.id) : parent : find_subid(id, parent.childArray);

答案 5 :(得分:0)

我会尽量不要重新发明轮子。我们将object-scan用于所有数据处理需求。从概念上讲,它很简单,但是可以容纳很多很酷的东西。这是解决特定问题的方法

数据定义

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

逻辑

const findNode = (id, input) => {
  let obj = null;
  objectScan(['**.id'], {
    filterFn: (key, value, { parents }) => {
      if (value === id) {
        obj = parents[0];
      }
    },
    breakFn: () => obj !== null
  })(data);
  return obj;
};

const result = findNode('6', data);

输出

// result =>
{
  "id": "6",
  "children": []
}

答案 6 :(得分:0)

递归结构搜索,修改,键/值调整/替换。

用法示例:

const results = []; // to store the search results

mapNodesRecursively(obj, ({ v, key, obj, isCircular }) => {
    // do something cool with "v" (or key, or obj)
    // return nothing (undefined) to keep the original value

    // if we search:
    if (key === 'name' && v === 'Roman'){
        results.push(obj);
    }

    // more example flow:
    if (isCircular) {
        delete obj[key]; // optionally - we decide to remove circular links
    } else if (v === 'Russia') {
        return 'RU';
    } else if (key.toLocaleLowerCase() === 'foo') {
        return 'BAR';
    } else if (key === 'bad_key') {
        delete obj[key];
        obj['good_key'] = v;
    } else {
        return v; // or undefined, same effect
    }
});

提示和提示:

您可以将其用作搜索回调,只需不返回任何内容(不会影响任何内容),然后为Array / Set / Map选择所需的值即可。

请注意,回调在每个叶子/值/键(不仅是对象)上运行。

或者您可以使用回调来调整特定值,甚至更改键。它还会自动检测循环,并提供标记供您决定如何处理它们。

代码

(使用ES6)

函数本身+一些示例演示数据

function mapNodesRecursively(obj, mapCallback, { wereSet } = {}) {
    if (!wereSet) {
        wereSet = new Set();
    }

    if (obj && (obj === Object(obj) || Array.isArray(obj))) {
        wereSet.add(obj);

        for (let key in obj) {
            if (!obj.hasOwnProperty(key)){
                continue;
            }

            let v = obj[key];

            const isCircular = wereSet.has(v);

            const mapped = mapCallback({ v, key, obj, isCircular });
            if (typeof (mapped) !== 'undefined') {
                obj[key] = mapped;
                v = mapped;
            }

            if (!isCircular) {
                mapNodesRecursively(v, mapCallback, { wereSet });
            }
        }
    }

    return obj;
}

let obj = {
    team: [
        {
            name: 'Roman',
            country: 'Russia',
            bad_key: 123,
        },
        {
            name: 'Igor',
            country: 'Ukraine',
            FOO: 'what?',
        },
        {
            someBool: true,
            country: 'Russia',
        },
        123,
        [
            1,
            {
                country: 'Russia',
                just: 'a nested thing',
                a: [{
                    bad_key: [{
                        country: 'Russia',
                        foo: false,
                    }],
                }],
            },
        ],
    ],
};

// output the initial data
document.getElementById('jsInput').innerHTML = JSON.stringify(obj, null, 2);

// adding some circular link (to fix with our callback)
obj.team[1].loop = obj;

mapNodesRecursively(obj, ({ v, key, obj, isCircular }) => {
    if (isCircular) {
        delete obj[key]; // optionally - we decide to remove circular links
    } else if (v === 'Russia') {
        return 'RU';
    } else if (key.toLocaleLowerCase() === 'foo') {
        return 'BAR';
    } else if (key === 'bad_key') {
        delete obj[key];
        obj['good_key'] = v;
    } else {
        return v;
    }
});

// output the result - processed object
document.getElementById('jsOutput').innerHTML = JSON.stringify(obj, null, 2);
.col {
  display: inline-block;
  width: 40%;
}
<div>
  <h3>Recursive structure modification, keys/values adjustments/replacement</h3>
  <ol>
    <li>
      Replacing "Russia" values with "RU"
    </li>
    <li>
      Setting the value "BAR" for keys "FOO"
    </li>
    <li>
      Changing the key "bad_key" to "good_key"
    </li>
  </ol>
  <div class="col">
    <h4>BEFORE</h4>
    <pre id="jsInput"></pre>
  </div>
  <div class="col">
    <h4>AFTER</h4>
    <pre id="jsOutput"></pre>
  </div>
</div>