JavaScript recursive search in JSON object

后端 未结 6 461
野趣味
野趣味 2020-11-30 04:21

I am trying to return a specific node in a JSON object structure which looks like this

{
    \"id\":\"0\",
    \"children\":[
        {
            \"id\":\"         


        
相关标签:
6条回答
  • 2020-11-30 04:26

    When searching recursively, you have to pass the result back by returning it. You're not returning the result of findNode(id, currentChild), though.

    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;
        }
    }
    
    0 讨论(0)
  • 2020-11-30 04:36

    I really liked a tree search! A tree is an extremely common data structure for most of today's complex structured tasks. So I just had similar task for lunch too. I even did some deep research, but havent actually found anything new! So what I've got for you today, is "How I implemented that in modern JS syntax":

    // 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);
    
    0 讨论(0)
  • 2020-11-30 04:43

    I use the following

    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;
    }
    

    Then you can write

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

    Now this works on circular references and you can match on any field or combination of fields you like by changing the matchCallback function.

    0 讨论(0)
  • 2020-11-30 04:44
    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));
    

    This method will return the node if it present in the node list. But this will loop through all the child of a node since we can't successfully break the forEach flow. A better implementation would look like below.

    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));
    
    0 讨论(0)
  • 2020-11-30 04:44

    I would try not to reinvent the wheel. We use object-scan for all our data processing needs. It's conceptually very simple, but allows for a lot of cool stuff. Here is how you would solve your specific question

    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: [] }
    
    0 讨论(0)
  • 2020-11-30 04:45

    Recursive structure search, modification, keys/values adjustments/replacement.

    Usage Example:

    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
        }
    });
    

    Tips and hints:

    You can use it as a search callback, just return nothing (won't affect anything) and pick values you need to your Array/Set/Map.

    Notice that callback is being run on every leaf/value/key (not just objects).

    Or you can use the callback to adjust particular values and even change keys. Also it automatically detects circular loops and provides a flag for you to decide how to handle them.

    The code

    (uses ES6)

    Function itself + some example demo data

    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>

    0 讨论(0)
提交回复
热议问题