Question

I am trying to generate URLs for pages stored in a MongoDB in node.

Using the following function I want to traverse a javascript object that and display the path to each element.

I am nearly there, but I am stuck - There might even be a better way to do this using using Async (which I must admit, confuses me a bit).

Function: (demo)

function printTree(people, slug) {
    for (var p = 0; p < people.length; p++) {
        var root = people[p];
        slug = slug + root.name + "/";
        console.log(slug);
        if (root.children.length > 0) {
            var childrenCount = root.children.length;
            for (var c = 0; c < childrenCount; c++) {
                if (root.children[c].children.length > 0) {
                    printTree(root.children[c].children, slug + root.children[c].name + "/");
                }
            }
        }
    }
};

Output:

/michael/
/michael/angela/oscar
/michael/meredith/creed
/michael/meredith/creed/kelly

Expected Output:

/michael/
/michael/angela/
/michael/angela/oscar/
/michael/meredith/
/michael/meredith/creed/
/michael/meredith/kelly/

Object:

[
  {
    "name": "michael",
    ...
    "children": [
      {
        "name": "angela",
        ...
        "children": [
          {
            "name": "oscar",
            ...
            "children": []
          }
        ]
      },
      {
        "name": "meredith",
        ...
        "children": [
          {
            "name": "creed",
            ...
            "children": []
          },
          {
            "name": "kelly",
            ...
            "children": []
          }
        ]
      },
      { ... }
    ]
  }
]

If it helps, the data is stored using nested sets: https://github.com/groupdock/mongoose-nested-set So there might be a better way to do the above work using nested sets (negating the above object).

Was it helpful?

Solution

Here you go. You don't need a second for loop, since your printTree function is going to loop through everything anyway (demo).

function printTree(people, slug){
  slug = slug || '/';
  for(var i = 0; i < people.length; i++) {
    console.log(slug + people[i].name + '/');
    if(people[i].children.length){
      printTree(people[i].children, slug + people[i].name + '/')
    }
  }
}

OTHER TIPS

You could also consider something in ECMA5 like this, in case you have further use of the tree or want to use some a seperator other than /. Nothing wrong with @bioball answer, this just gives you some more flexibility if wanted.

function makeTree(people, slug, sep) {
    slug = slug || '/';
    sep = sep || slug;
    return people.reduce(function (tree, person) {
        var slugPerson = slug + person.name + sep;

        return tree.concat(slugPerson, makeTree(person.children, slugPerson, sep));
    }, []);
}

function printTree(tree) {
    tree.forEach(function (path) {
        console.log(path);
    });
}

printTree(makeTree(data));

On jsFiddle

Not a big fan of reinventing the wheel, so here is a solution using a object-scan. We use it for many data processing tasks and really like it because it makes things easier to maintain. However there is a learning curve. Anyways, here is how you could solve your question

// const objectScan = require('object-scan');

const scanTree = (tree) => objectScan(['**.children'], {
  reverse: false,
  breakFn: ({ isMatch, parents, context }) => {
    if (!isMatch) {
      return
    }
    context.push(
      `/${parents
        .filter((p) => 'name' in p)
        .map(({ name }) => name)
        .reverse()
        .join('/')}/`
    );
  }
})(tree, []);

const tree = [{ id: '52fc69975ba8400021da5c7a', name: 'michael', children: [{ id: '52fc69975ba8400021da5c7d', parentId: '52fc69975ba8400021da5c7a', name: 'angela', children: [{ id: '52fc69975ba8400021da5c83', parentId: '52fc69975ba8400021da5c7d', name: 'oscar', children: [] }] }, { id: '52fc69975ba8400021da5c7b', parentId: '52fc69975ba8400021da5c7a', name: 'meredith', children: [{ id: '52fc69975ba8400021da5c7f', parentId: '52fc69975ba8400021da5c7b', name: 'creed', children: [] }, { id: '52fc69975ba8400021da5c7e', parentId: '52fc69975ba8400021da5c7b', name: 'kelly', children: [] }] }, { id: '52fc69975ba8400021da5c7c', parentId: '52fc69975ba8400021da5c7a', name: 'jim', children: [{ id: '52fc69975ba8400021da5c82', parentId: '52fc69975ba8400021da5c7c', name: 'dwight', children: [] }, { id: '52fc69975ba8400021da5c80', parentId: '52fc69975ba8400021da5c7c', name: 'phyllis', children: [] }, { id: '52fc69975ba8400021da5c81', parentId: '52fc69975ba8400021da5c7c', name: 'stanley', children: [] }] }] }];

scanTree(tree).map((e) => console.log(e));
// => /michael/
// => /michael/angela/
// => /michael/angela/oscar/
// => /michael/meredith/
// => /michael/meredith/creed/
// => /michael/meredith/kelly/
// => /michael/jim/
// => /michael/jim/dwight/
// => /michael/jim/phyllis/
// => /michael/jim/stanley/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>

Disclaimer: I'm the author of object-scan

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top