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
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>
3 Comments
innerHTML when we're done processing the object?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)