JSON对象中的JavaScript递归搜索

Posted

技术标签:

【中文标题】JSON对象中的JavaScript递归搜索【英文标题】:JavaScript recursive search in JSON object 【发布时间】:2014-04-08 23:27:43 【问题描述】:

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

请帮忙!

【问题讨论】:

既然是答案,我只想指出 foreach 循环不能在 javascript 中停止。不要在算法中使用 foreach。 为什么首先要对 JSON 对象执行搜索?您也许应该考虑在生成 JSON 对象的地方进行搜索,希望是数据库。 @jmb.mage 因为在现实世界中,您经常需要解决没有理想环境且细节无法触及的任务。这是其中之一。 【参考方案1】:

当递归搜索时,您必须通过返回结果将结果传回。不过,您不会返回 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;
    

【讨论】:

谢谢!完美运行。 我必须在 for 循环中添加一个额外的检查(即 for (i = 0; currentNode.children !== undefined && i 因找不到未定义的长度而中断。它是一个文档对象。 我现在正在 Premierepro 扩展脚本的所有垃圾箱中按名字搜索一个孩子 :) 这是一个类似的解决方案,但代码更少***.com/a/52205610/3666971【参考方案2】:
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));

【讨论】:

亲爱的@Triode,您定义了result,但从不使用它。 @Mehr88sh 抱歉,已更正!!【参考方案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 函数来匹配您喜欢的任何字段或字段组合。

【讨论】:

匹配回调是个好主意,我也喜欢你得到路径和对象。干杯! 它有效,谢谢。您应该提到,为了搜索特定节点,您应该更改此部分: if (property.indexOf("NameOfTheNodeYouAreLookingFor") !== 0) 并且还要更改为返回对象;而不是返回结果; @atfede 我返回result 的原因是允许一次以上的点击。我有点不确定你说的if (property.indexOf("NameOfTheNodeYouAreLookingFor") !== 0) 是什么意思【参考方案4】:

由于这个老问题已经被重新提出,这里有一种不同的方法。我们可以编写一个相当通用的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
  )

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

【讨论】:

【参考方案5】:

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

// 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
&lt;script src="https://bundle.run/object-scan@13.8.0"&gt;&lt;/script&gt;

免责声明:我是object-scan的作者

【讨论】:

这里的其他几个答案提到它们适用于圆形对象。对象扫描是否考虑到这一点? 是的。但是,由于这是一种性能权衡,您需要明确地处理它。即你可以简单地添加breakFn: ( isCircular ) =&gt; isCircular 我很喜欢简单的对象扫描解决这个问题。我还不确定我想在自己的代码中尝试它,不过我可能很快就会到达那里。【参考方案6】:

我真的很喜欢树搜索!对于当今大多数复杂的结构化任务,树是一种极其常见的数据结构。所以我午餐也有类似的任务。我什至做了一些深入的研究,但实际上还没有发现任何新东西!所以我今天要给你的是“我是如何用现代 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);

【讨论】:

【参考方案7】:

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

使用示例:

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>

【讨论】:

以上是关于JSON对象中的JavaScript递归搜索的主要内容,如果未能解决你的问题,请参考以下文章

javascript的JSON对象

如何使用 JSON 数据递归填充 TreeView

javaScript中的对象比较是线性时间还是常数时间?

递归 JavaScript 函数正在丢失返回值

处理 PHP JSON 对象中的数据

XML和JSO的面试题(修订版)