1

I have a nested object like this:

const countries = {
  "Europe": {
    "France": {},
    "Spain": {}
  },
  "America": {
    "North": {
      "USA": {},
      "Canada": {}
    },
    "South": {
      "Brazil": {},
      "Argentina": {}
    }
  }
};

And I want to create an unordered list from it like this one:

<ul>
  <li>
    Europe:
    <ul>
      <li>France</li>
      <li>Spain</li>
    </ul>
  </li>
  <li>
    America:
    <ul>
      <li>
        North:
        <ul>
          <li>USA</li>
          <li>Canada</li>
        </ul>
      </li>
      <li>
        South:
        <ul>
          <li>Brazil</li>
          <li>Argentina</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

And it shouldn't have any empty lists in the end.

So far I tried this recursive approach, but it just returns a list with 2 items [object Object]:

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>data tree</title>
  </head>
  <body>
    <div id="container">

    </div>
    <script type="text/javascript">
      const countries = {
        "Europe": {
          "France": {},
          "Spain": {}
        },
        "America": {
          "North": {
            "USA": {},
            "Canada": {}
          },
          "South": {
            "Brazil": {},
            "Argentina": {}
          }
        }
      };

      //Getting the container were I want to put my list
      let container = document.getElementById('container');

      function createTree(container, data) {
        //Recursive function which will create as much lists as I need
        function rec(obj) {
          let list = document.createElement('ul');
          //Looping through the object properties
          for (let item in obj){
            //If the object property is object too
            //And it has its own properties
            //Then create a list ite for it 
            //And put a new list in it with the recursion
            if (Object.keys(obj[item]).length) {
              let listItem = document.createElement('li');
              listItem.textContent += obj[item];
              list.appendChild(listItem);
              rec(obj[item]);
            }
          }
          
          return list;
        }

        //In the end add the list to the container
        container.appendChild(rec(data));
      }

      createTree(container, countries);
    </script>
  </body>
</html>

If there's any way to do it with other approaches like loops or anything else, it would be acceptable too.

Thanks in advance.

2 Answers 2

3

You need to assign to the content of the created li on each iteration regardless - then check if the associated object has any keys, and if so, perform the recursive call:

const countries = {
  "Europe": {
    "France": {},
    "Spain": {}
  },
  "America": {
    "North": {
      "USA": {},
      "Canada": {}
    },
    "South": {
      "Brazil": {},
      "Argentina": {}
    }
  }
};

function createTree(container, data) {
  const ul = container.appendChild(document.createElement('ul'));
  for (const [key, val] of Object.entries(data)) {
    const li = ul.appendChild(document.createElement('li'));
    li.textContent = key;
    if (Object.keys(val).length) {
      createTree(li, val);
    }
  }
}

createTree(document.getElementById('container'), countries);
<div id="container">

</div>

I guess if you really wanted to you could use a DocumentFragment instead:

const countries = {
  "Europe": {
    "France": {},
    "Spain": {}
  },
  "America": {
    "North": {
      "USA": {},
      "Canada": {}
    },
    "South": {
      "Brazil": {},
      "Argentina": {}
    }
  }
};

function createTree(container, data) {
  const ul = container.appendChild(document.createElement('ul'));
  for (const [key, val] of Object.entries(data)) {
    const li = ul.appendChild(document.createElement('li'));
    li.textContent = key;
    if (Object.keys(val).length) {
      createTree(li, val);
    }
  }
}

const frag = document.createDocumentFragment();
createTree(frag, countries);
document.getElementById('container').appendChild(frag);
<div id="container">

</div>

Sign up to request clarification or add additional context in comments.

3 Comments

If performance isn't a concern this is probably just fine. However if it is, is it worth considering using a document fragment or just a plain old innerHTML when we're done processing the object?
Unless there are literally tens of thousands of nodes, it shouldn't matter. Performance isn't an issue in 99% of situations - better to concentrate on clean, short, readable code. The browser will only repaint once all Javascript is completely finished anyway.
It seems like I was in the right way, but I went too far and made it very hard for me. Thank you very much, it looks way cleaner and readable!
0

Just to add to CertainPerformance's answer. I tried with a nested object tree which had some properties with string values like:

var k1 = {
  k11:{
     k111:'111'
     }
  }

and the recursion crashed the chrome-page.

So I changed this:

li.textContent = key;
if (Object.keys(val).length) {
    createTree(li, val);
}

to this:

if (Object.keys(val).length && isObj(val)){
    li.textContent = key;
    createTree(li,val);
} else {
    li.textContent = key + ' : ' + val;
}

and added:

isObj = function(obj) {return obj === Object(obj);}

(before the createTree function)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.