2

I am trying to recursively load a scenegraph structure from a JSON file. My idea was to call the same function on all child nodes until the last node has no children. However when debugging the code I found that the recursive call of loadNodes inside the second loop is being ignored and instead the loop just increases the counter and starts with the next loop.

I checked my syntax and I check calling other functions inside the loop (which works)..

Does anybody have an idea on what I am doing wrong?

function loadNodes(obj, current_group_node) {
    for (let i = 0; i < obj.length; i++) {
        if (obj[i].nodetype === 'group') {
            let new_group_node = new GroupNode(new Matrix(obj[i].matrix));
            current_group_node.add(new_group_node);
            for (let j = 0; j < obj[i].children.length; j++) {
                loadNodes(obj[i].children[j], new_group_node);
            }
        } else {
           // some more logic

        }
    }
}

My function receives an array of objects with the following possible structure:

{
  "nodetype": "group",
  "name": "root",
  "matrix": [
    1,0,0,0,
    0,1,0,0,
    0,0,1,0,
    0,0,0,1
  ],
  "children": [
    {
      "nodetype": "group",
      "name": "driver-cube-group",
      "matrix": [
        1,0,0,0,
        0,1,0,0,
        0,0,1,0,
        0,0,0,1
      ],
      "children": [
        {
          "nodetype": "aabox",
          "name": "driver-cube",
          "minpoint": {
            "x": -3,
            "y": -3,
            "z": -3,
            "w": 1
          },
          "maxpoint": {
            "x": -2,
            "y": -2,
            "z": -2,
            "w": 1
          },
          "color": {
            "x": 1,
            "y": 1,
            "z": 1,
            "w": 1
          }
        }
      ]
    }
  ]
}

2 Answers2

2

loadNodes expects the first argument to be an array of objects, not a single object. You don't need to call it in a loop on each child object, just call it once, passing the child array as the argument. It does its own looping over the array.

So replace this:

        for (let j = 0; j < obj[i].children.length; j++) {
            loadNodes(obj[i].children[j], new_group_node);
        }

with:

        loadNodes(obj[i].children, new_group_node);

It might be helpful to rename the first argument from obj to arr, to make it clear that it expects an array, not a single object.

Barmar
  • 741,623
  • 53
  • 500
  • 612
0

Providing the JSON is passed to your function as you have posted it, I have come up with the following solution (I have changed some of the nomenclature and also replaced two instantiations by creating an object and assigning an array to it for nested objects):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xml:lang="en">
<head>
<title>JSON Test Case</title>
<script type="application/javascript">
/* <![CDATA[ */
'use strict';

var nested = 0;

function loadNodes(p_obj, p_current_group_node)
  {
let l_i;
let l_new_group_node;

  nested++;

  console.log('Entering loadNodes (' + nested + ')...');
  console.log('Length of object: ' + p_obj.length);     // Oops...!
  console.log('Type of JSON object: ' + typeof p_obj);
  console.log('Determining the node type: ' + p_obj.nodetype);
  console.log('Node name: ' + p_obj.name);
  if(p_obj.nodetype == 'group')
    {
    console.log('Number of elements: ' + p_obj.children.length);

    for(l_i = 0; l_i < p_obj.children.length; l_i++)
      {
      console.log('Found a subtree for processing!');

      l_new_group_node = new Object();
      l_new_group_node.nodes = new Array();
      p_current_group_node.nodes.push(l_new_group_node);
      loadNodes(p_obj.children[l_i], l_new_group_node);
      }
    }
  else
    console.log('Process leaf node here...');

  console.log('Leaving loadNodes (' + nested + ')...');
  nested--;
  }
/* ]]> */
</script>
</head>
<body>
<header><h1>JSON test case</h1></header>
<main>
<script type="application/javascript">
/* <![CDATA[ */
var json = {
  "nodetype": "group",
  "name": "root",
  "matrix": [
    1,0,0,0,
    0,1,0,0,
    0,0,1,0,
    0,0,0,1
  ],
  "children": [
    {
      "nodetype": "group",
      "name": "driver-cube-group",
      "matrix": [
        1,0,0,0,
        0,1,0,0,
        0,0,1,0,
        0,0,0,1
      ],
      "children": [
        {
          "nodetype": "aabox",
          "name": "driver-cube",
          "minpoint": {
            "x": -3,
            "y": -3,
            "z": -3,
            "w": 1
          },
          "maxpoint": {
            "x": -2,
            "y": -2,
            "z": -2,
            "w": 1
          },
          "color": {
            "x": 1,
            "y": 1,
            "z": 1,
            "w": 1
          }
        }
      ]
    }
  ]
}
var myobj = new Object();
myobj.nodes = new Array();

loadNodes(json, myobj);
/* ]]> */
</script>
</main>
</body>
</html>

First of all, I could toss out the outer loop, because that doesn't even fire up and so prevents the recursion (I have found that p_obj.length even returns undefined if you are trying to access this property). After all, it's an object, and that obviously doesn't provide a length. Instead I am checking the parameters of the object that we have received and then determine if we need to descend any further or have actually reached a leaf node.

If we need to descend any further, there's a loop in effect to dig through the array assigned to the property children - that actually has a length so we can take that for our termination condition.

If you want to see what's actually happening, you can run this test case with your browser's console open. I have inserted various statements that log particular information to it so you can see what's being done here.
You should also pay attention to the entries Entering loadNodes (...) and Leaving loadNodes (...), because they tell you where recursion is actually taking place and when the recursively invoked function is left. You can find the nesting level in the parentheseses.

Robidu
  • 576
  • 3
  • 17
  • The question says that he's passing an array of objects like the one he showed. – Barmar Sep 03 '18 at 23:08
  • If you're going to show the full code and HTML, why not make it an executable stack snippet? – Barmar Sep 03 '18 at 23:09
  • Then please see my code for a working test case - and have a look at the console for execution details... – Robidu Sep 03 '18 at 23:10
  • Plus it may state that an array is passed to loadNodes, but I rather see an object containing the data. – Robidu Sep 03 '18 at 23:11
  • It says "My function receives an array of objects with the following possible structure". I interpret that to mean that he's showing an example of one array element, not the whole function parameter. I admit that this is a confusing way to write the question, and it would be better if he'd shown the whole parameter. – Barmar Sep 03 '18 at 23:12
  • Sure. That would require some retooling of my code, though. – Robidu Sep 03 '18 at 23:14