简体   繁体   中英

Javascript recursion caused circular structure

I have the following 2D array cells :

ID  Name    Parent
1   Bob     0
2   Alice   1
3   John    2
4   Jane    2
5   Jenny   3
6   Jonny   2

I want to convert it into nested JSON format, such that each object has the following attributes:

  1. name

  2. Array of children, which includes also objects with names and arrays of children. There are no circular nestings in the array; a child can't have any of its parents as children.

Here is the function I wrote:

function getChildren(node){
    console.log('Getting Children for' + node)
    children = []
    for(i = 0;i < cells.length; i++){
        if(cells[i][2] == node){
            cell = cells[i]
            child = {}
            child.name = cell[1]
            child.children = getChildren(cell[0])
            children.push(child)
        }
    }
    return children
}


text = "ID  Name    Parent\n1   Bob     0\n2   Alice   1\n3   John                  2\n4   Jane    2\n5   Jenny     3\n6   Jonny   2\n"

lines = text.split('\n')
cells = []
for(i = 0; i < lines.length; i++){
    cells[i] = lines[i].split(/\ +/)
}

Calling the function on node 6 getChildren(6) , gives this output:

Getting Children for6
[]

Which is correct because node 6 has no children.

But calling the function on nodes with children, for example getChildren(3) , gives:

Getting Children for3
Getting Children for5

 Object children: Array[1] 0: Object children: Array[1] 0: Object children: Array[1] name: "Jenny" length: 1 name: "Jenny" length: 1 name: "Jenny" 

From the console output it seems like it calls the right functions, but why is the object for "jenny" infinitely nested under all children?

I want to end up with a JSON object that I could use JSON.stringify on. Calling the function on getChildren(3) gives the error

Uncaught TypeError: Converting circular structure to JSON.

I think this is because the object for Jenny is infinitely nested under each child.

Change your function to the following

function getChildren(node){
    console.log('Getting Children for' + node)
    var children = []
    for(i = 0;i<cells.length; i++){
        if(cells[i][2] == node){
            var cell = cells[i]
            child = {}
            child.name = cell[1]
            child.children = getChildren(cell[0])
            children.push(child)
        }}
    return children
}

Note the added "var"'s before the variable declarations. This makes sure they are reinitialized instead of persisting through functions calls. That's what was causing your issue.

You use global variables, and as a consequence, when you call your function recursively, variables like children and child can get new values. When you come back out of the recursive call, you do:

children.push(child)

.. but children will have taken another value than you expect, and so can child also have a different value, both from what they were in the recursive call (or from even deeper into the recursion).

For the same reason the recursive modification of i will lead to problems.

Use var to make your variables local to your function, and it will work:

function getChildren(node){
    console.log('Getting Children for' + node)
    var children = []
    for(var i = 0;i<cells.length; i++){
        if(cells[i][2] == node){
            var cell = cells[i]
            var child = {}
            child.name = cell[1]
            child.children = getChildren(cell[0])
            children.push(child)
        }}
    return children
}

 function getChildren(node){ var children = [] for(var i = 0;i<cells.length; i++){ if(cells[i][2] == node){ var cell = cells[i] var child = {} child.name = cell[1] child.children = getChildren(cell[0]) children.push(child) }} return children } var text = "ID Name Parent\\n1 Bob 0\\n2 Alice 1\\n3 John 2\\n4 Jane 2\\n5 Jenny 3\\n6 Jonny 2\\n" var lines = text.split('\\n') var cells = [] for(var i = 0; i< lines.length; i++){ cells[i] = lines[i].split(/\\ +/) } console.log(JSON.stringify(getChildren(0), null, 2)); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

if I understand right you can use another way to parse your data:

//separete the values
var input = [
    [1,   'Bob',     0],
    [2,   'Alice',   1],
    [3,   'John',    2],
    [4,   'Jane',    2],
    [5,   'Jenny',   3],
    [6,   'Jonny',   2]
];

var entities = []; //I belive that the ids are uniq

for (var i = 0; i < input.length; i++){
    var id = input[i][0];
  entities[id] = {
    id: id, 
    name: input[i][1], 
    children : [], 
    parent: input[i][2]
  };
}

for (var i in entities){
    var current = entities[i];
  var parent = entities[current.parent];
  delete current.parent;

  if (parent){
        parent.children.push(current);
  }
}

By this way you can find the particular element from the entities array by indexes, or at the begin of parsing get the root element (the element which does not contain its parent in the elements array)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM