Сперва превращаем плоский массив во вложенный:
function createTreeData(arr, idKey, parentKey) {
const tree = Object.fromEntries(arr.map(n => [ n[idKey], { ...n, children: [] } ]));
return Object.values(tree).filter(n => !tree[n[parentKey]]?.children.push(n));
}
const treeData = createTreeData(data, 'id', 'parent_id');
Затем можно собрать разметку дерева:
const createTreeHTML = data =>
Array.isArray(data) && data.length
? `<ul>${data.map(n => `
<li>
${n.name}
${createTreeHTML(n.children)}
</li>`).join('')}
</ul>`
: '';
document.body.insertAdjacentHTML('beforeend', createTreeHTML(treeData));
Или, создавать элементы напрямую:
const createTreeElement = data =>
data instanceof Array && data.length
? data.reduce((ul, n) => (
ul.append(document.createElement('li')),
ul.lastChild.append(n.name, createTreeElement(n.children)),
ul
), document.createElement('ul'))
: '';
document.body.append(createTreeElement(treeData));
ИЛИ
Без создания промежуточного вложенного массива:
function createTreeElement(arr, idKey, parentKey) {
const tree = arr.reduce((acc, { [parentKey]: n }) => (
acc[n] = acc[n] ?? document.createElement('ul'),
acc
), {});
arr.forEach(n => (
tree[n[parentKey]].append(document.createElement('li')),
tree[n[parentKey]].lastChild.append(n.name, tree[n[idKey]] ?? '')
));
return Object.values(tree).reduce((ul, n) => (
n.parentNode || ul.append(...n.children),
ul
), document.createElement('ul'));
// или, если не надо объединять в общий список элементы, у которых разные корневые parent_id
// return Object.values(tree).filter(n => !n.parentNode);
}
document.body.append(createTreeElement(data, 'id', 'parent_id'));