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.

asked May 11, 2020 at 9:07

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>

answered May 11, 2020 at 9:13
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)

answered Jul 21, 2022 at 20:39

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.