How to create nested child objects in JavaScript from array? - javascript

This is the given array:
[{
key: 1,
nodes: {}
}, {
key: 2,
nodes: {}
}, {
key: 3,
nodes: {}
}]
How to create nested child objects in JavaScript from this array?
[{
key: 1,
nodes: [{
key: 2,
nodes: [{
key: 3,
nodes: []
}]
}]
}];

This is a pretty good use case for reduceRight which allows you to build the structure from the inside out:
let arr = [{
key: 1,
nodes: {}
}, {
key: 2,
nodes: {}
}, {
key: 3,
nodes: {}
}]
let a = arr.reduceRight((arr, {key}) => [{key, nodes: arr}],[])
console.log(a)

It's working fine. Try this below code
const firstArray = [{ key: 1, nodes: {} }, { key: 2, nodes: {} }, { key: 3, nodes: {} }];
firstArray.reverse();
const nestedObject = firstArray.reduce((prev, current) => {
return {
...current,
nodes:[{...prev}]
}
}, {});
console.log(nestedObject)

Related

How to convert tree having nodes stored as key-value objects to a tree structure having nodes stored as array of objects in Javascript?

I am trying to convert a tree structure having nodes stored as key-value objects to a tree structure having nodes stored as array of objects. How could I achieve it?
Example: I would like to convert this structure:
const treeX = [
{
id: '7d2a730a-53b7-4ce6-b791-446a7dc3d97e',
name: 'category1',
nodes: {
'd043f4bd-6d3f-478c-b4ea-a5fc1ad66fb1': {
name: 'subcategory2',
id: 'd043f4bd-6d3f-478c-b4ea-a5fc1ad66fb1',
nodes: {
'69c7481d-d8c3-41da-b8fa-84d6056f6344': {
name: 'subsubcategory3',
id: '69c7481d-d8c3-41da-b8fa-84d6056f6344',
nodes: {},
},
'184e0b3d-108c-45ee-8c62-a81b73bf5b7b': {
name: 'subsubcategory4',
id: '184e0b3d-108c-45ee-8c62-a81b73bf5b7b',
nodes: {},
},
},
},
'0ec9a897-6e06-4c06-a780-b37ce8fd51f7': {
name: 'subcategory5',
id: '0ec9a897-6e06-4c06-a780-b37ce8fd51f7',
nodes: {
'9531f8d4-0d28-4a89-8d4e-ddd3ceea4939': {
name: 'subsubcategory6',
id: '9531f8d4-0d28-4a89-8d4e-ddd3ceea4939',
nodes: {},
},
'2f32f5b8-0abb-48e1-b6ad-b7f00aba02ca': {
name: 'subsubcategory7',
id: '2f32f5b8-0abb-48e1-b6ad-b7f00aba02ca',
nodes: {},
},
},
},
},
},
{
id: '985ab20a-53b7-4ce6-b791-446a7dc3d97e',
name: 'category8',
nodes: {
'26b97abd-6d3f-478c-b4ea-a5fc1ad66fb1': {
name: 'subcategory9',
id: '236b45bd-6d3f-478c-b4ea-a5fc1ad66fb1',
nodes: {},
},
},
},
];
to:
const treeY = [
{
id: '7d2a730a-53b7-4ce6-b791-446a7dc3d97e',
name: 'category1',
nodes: [
{
name: 'subcategory2',
id: 'd043f4bd-6d3f-478c-b4ea-a5fc1ad66fb1',
nodes: [
{
name: 'subsubcategory3',
id: '69c7481d-d8c3-41da-b8fa-84d6056f6344',
nodes: [],
},
{
name: 'subsubcategory4',
id: '184e0b3d-108c-45ee-8c62-a81b73bf5b7b',
nodes: [],
},
],
},
{
name: 'subcategory5',
id: '0ec9a897-6e06-4c06-a780-b37ce8fd51f7',
nodes: [
{
name: 'subsubcategory6',
id: '9531f8d4-0d28-4a89-8d4e-ddd3ceea4939',
nodes: [],
},
{
name: 'subsubcategory7',
id: '2f32f5b8-0abb-48e1-b6ad-b7f00aba02ca',
nodes: [],
},
],
},
],
},
{
id: '985ab20a-53b7-4ce6-b791-446a7dc3d97e',
name: 'category8',
nodes: [
{
name: 'subcategory9',
id: '236b45bd-6d3f-478c-b4ea-a5fc1ad66fb1',
nodes: [],
},
],
},
];
The way how I've achieved it:
categories.forEach(category => {
category.nodes = Object.values(category.nodes);
category.nodes?.forEach(subcategory => {
subcategory.nodes = Object.values(subcategory.nodes);
subcategory.nodes?.forEach(subsubcategory => {
subsubcategory.nodes = Object.values(subsubcategory.nodes);
subsubcategory.nodes?.forEach(subsubsubcategory => {
subsubsubcategory.nodes = Object.values(subsubsubcategory.nodes)
})
})
})
});
I know that the solution is absolutely uneffective and it won't work if the tree would have more levels. Therefore, I would like to ask, how can I improve the code? Maybe using recursive function?
Looks to me all you really need is to recursively map the Object.values of the nodes property:
const treeX=[{id:"7d2a730a-53b7-4ce6-b791-446a7dc3d97e",name:"category1",nodes:{"d043f4bd-6d3f-478c-b4ea-a5fc1ad66fb1":{name:"subcategory2",id:"d043f4bd-6d3f-478c-b4ea-a5fc1ad66fb1",nodes:{"69c7481d-d8c3-41da-b8fa-84d6056f6344":{name:"subsubcategory3",id:"69c7481d-d8c3-41da-b8fa-84d6056f6344",nodes:{}},"184e0b3d-108c-45ee-8c62-a81b73bf5b7b":{name:"subsubcategory4",id:"184e0b3d-108c-45ee-8c62-a81b73bf5b7b",nodes:{}}}},"0ec9a897-6e06-4c06-a780-b37ce8fd51f7":{name:"subcategory5",id:"0ec9a897-6e06-4c06-a780-b37ce8fd51f7",nodes:{"9531f8d4-0d28-4a89-8d4e-ddd3ceea4939":{name:"subsubcategory6",id:"9531f8d4-0d28-4a89-8d4e-ddd3ceea4939",nodes:{}},"2f32f5b8-0abb-48e1-b6ad-b7f00aba02ca":{name:"subsubcategory7",id:"2f32f5b8-0abb-48e1-b6ad-b7f00aba02ca",nodes:{}}}}}},{id:"985ab20a-53b7-4ce6-b791-446a7dc3d97e",name:"category8",nodes:{"26b97abd-6d3f-478c-b4ea-a5fc1ad66fb1":{name:"subcategory9",id:"236b45bd-6d3f-478c-b4ea-a5fc1ad66fb1",nodes:{}}}}];
const transformObj = (obj) => ({
...obj,
nodes: Object.values(obj.nodes).map(transformObj)
});
const result = treeX.map(transformObj);
console.log(result);

How to convert json to tree array in JS?

I would like to convert this json / object to this specific structure below to allow me to use a treeList component.
I've tried to build a recursive function but I didn't find the solution yet.
Thanks for your help
const data = {
parent1: {
child1: { bar: "1" },
child2: "2"
},
parent2: {
child1: "1"
}
}
to
const treeData = [
{
title: "parent1",
key: "parent1",
children: [
{
title: "child1",
key: "child1",
children: [{ title: "bar", key: "bar", value: "1" }]
},
{
title: "child2",
key: "child2",
value: "2"
}
],
},
{
title: "parent2",
key: "parent2",
children: [
{
title: "child1",
key: "child1",
value: "1"
}
]
}
]
You could take an iterative and recursive approach.
function getNodes(object) {
return Object
.entries(object)
.map(([key, value]) => value && typeof value === 'object'
? { title: key, key, children: getNodes(value) }
: { title: key, key, value }
);
}
const data = { parent1: { child1: { bar: "1" }, child2: "2" }, parent2: { child1: "1" } },
result = getNodes(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
just share sample, a little different from yours. But it give you a hint with recursive function.
https://jsfiddle.net/segansoft/7bdxmys4/1/
function getNestedChildren(arr, parent) {
var out = []
for (var i in arr) {
if (arr[i].parent == parent) {
var children = getNestedChildren(arr, arr[i].id)
if (children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}
var flat = [{
id: 1,
title: 'hello',
parent: 0
},
{
id: 2,
title: 'hello',
parent: 0
},
{
id: 3,
title: 'hello',
parent: 1
},
{
id: 4,
title: 'hello',
parent: 3
},
{
id: 5,
title: 'hello',
parent: 4
},
{
id: 6,
title: 'hello',
parent: 4
},
{
id: 7,
title: 'hello',
parent: 3
},
{
id: 8,
title: 'hello',
parent: 2
}
]
var nested = getNestedChildren(flat, 0)
document.write('<pre>' + JSON.stringify(nested, 0, 4) + '</pre>');

Mapping multidimensional array of objects in JavaScript

I need to recreate a multidimensional array of objects only by looking at a single node.
I tried using recursive function in a loop(Array.map).
obj = [{
key: 0,
children: [{
key: 1,
children: [{
key: 2,
children: [{
key: 3,
children: []
},
{
key: 4,
children: []
}]
},
{
key: 5,
children: [{
key: 6,
children: []
},
{
key: 7,
children: []
},
{
key: 8,
children: []
}]
}]
}]
}]
function test(arg, level=0, arry=[]){
arg.map((data, i) => {
if(!data.arry){
arry.push(data.key);
data.arry = arry;
}
if(data.children){
test(data.children, level+1, data.arry);
}
})
}
test(obj);
Function test should build and return exactly the same object as obj.
This is only a simplified version of the problem I have and that's why it looks weird(returning an object I already have). My original problem is about fetching parts of n-dimensional array of objects from DB but without knowing its original dimensions. So I have to "discover" the dimensions and then build the exact same copy of the object.
An example implementation would be this: Recursively iterating over array and objects and deep-copying their properties/children. This creates a deep copy of an object, and tests that data is actually copied and not a reference anymore:
obj = [{
key: 0,
children: [{
key: 1,
children: [{
key: 2,
children: [{
key: 3,
children: []
},
{
key: 4,
children: []
}]
},
{
key: 5,
children: [{
key: 6,
children: []
},
{
key: 7,
children: []
},
{
key: 8,
children: []
}]
}]
}]
}];
function copy(obj) {
let copiedObject;
if (Array.isArray(obj)) {
// for arrays: deep-copy every child
copiedObject = [];
for (const child of obj) {
copiedObject.push(copy(child));
}
} else if (typeof obj === 'object') {
// for objects: deep-copy every property
copiedObject = {};
for (const key in obj) {
copiedObject[key] = copy(obj[key]);
}
} else {
// for primitives: copy the value
return obj;
}
return copiedObject;
}
function test() {
const cpy = copy(obj);
// make sure that deep-copying worked
console.log('Values from obj should be exactly copied to cpy: ' + (cpy[0].children[0].children[0].key === obj[0].children[0].children[0].key));
// make sure that references are broken, so that when data from the original object is updated, the updates do
// NOT reflect on the copy
obj[0].children[0].children[0].key = Math.random();
console.log('Changes to obj should not reflect on cpy: ' + (cpy[0].children[0].children[0].key !== obj[0].children[0].children[0].key));
}
test();

How do I find the path to a deeply nested unique key in an object and assign child objects accordingly?

With a given flat list:
let list = [
{
key: 1,
parent: null,
},
{
key: 2,
parent: 1,
},
{
key: 3,
parent: null,
},
{
key: 4,
parent: 1,
},
{
key: 5,
parent: 2,
}
]
How do I create a nested object like the one below?
let nest = {
children: [
{
key: 1,
children: [
{
key: 2,
children: [
{
key: 5,
children: []
}
]
},
{
key: 4,
children: []
}
]
},
{
key: 3,
children: []
}
]
}
I'm not sure how to approach this. The solution I have in mind would have to iterate over the list over and over again, to check if the object's parent is either null, in which case it gets assigned as a top-level object, or the object's parent already exists, in which case we get the path to the parent, and assign the child to that parent.
P.S.
I don't think this is a duplicate of any of the below
this checks for a key in a flat object.
this doesn't show anything that would return a path, given a unique key.
For building a tree, you could use a single loop approach, by using not only the given key for building a node, but using parent as well for building a node, where the dendency is obviously.
It uses an object where all keys are usesd as reference, like
{
1: {
key: 1,
children: [
{
/**id:4**/
key: 2,
children: [
{
/**id:6**/
key: 5,
children: []
}
]
},
{
/**id:8**/
key: 4,
children: []
}
]
},
2: /**ref:4**/,
3: {
key: 3,
children: []
},
4: /**ref:8**/,
5: /**ref:6**/
}
The main advantage beside the single loop is, it works with unsorted data, because of the structure to use keys and parent information together.
var list = [{ key: 1, parent: null, }, { key: 2, parent: 1, }, { key: 3, parent: null, }, { key: 4, parent: 1, }, { key: 5, parent: 2, }],
tree = function (data, root) {
var r = [], o = {};
data.forEach(function (a) {
var temp = { key: a.key };
temp.children = o[a.key] && o[a.key].children || [];
o[a.key] = temp;
if (a.parent === root) {
r.push(temp);
} else {
o[a.parent] = o[a.parent] || {};
o[a.parent].children = o[a.parent].children || [];
o[a.parent].children.push(temp);
}
});
return r;
}(list, null),
nest = { children: tree };
console.log(nest);
.as-console-wrapper { max-height: 100% !important; top: 0; }

filter nested tree object without losing structure

I have nested tree object I would like filter through without losing structure
var items = [
{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}]
}]
}
];
so for example if id == 2 remove object with id 2 and his children
if id == 3 only remove object with id 3
this's just apiece of object to make question clean but the object it self contains more and more :)
using vanilla javascript, _lodash or Angular2 it's okay
thank you
You can create recursive function using filter() and also continue filtering children if value is Array.
var items = [{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}, ]
}]
}];
function filterData(data, id) {
var r = data.filter(function(o) {
Object.keys(o).forEach(function(e) {
if (Array.isArray(o[e])) o[e] = filterData(o[e], id);
})
return o.id != id
})
return r;
}
console.log(filterData(items, 3))
console.log(filterData(items, 2))
Update: As Nina said if you know that children is property with array you don't need to loop keys you can directly target children property.
var items = [{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}, ]
}]
}];
const filterData = (data, id) => data.filter(o => {
if (o.children) o.children = filterData(o.children, id);
return o.id != id
})
console.log(JSON.stringify(filterData(items, 3), 0, 2))
console.log(JSON.stringify(filterData(items, 2), 0, 2))
If it's ok for your case to use Lodash+Deepdash, then:
let filtered = _.filterDeep(items,(i)=>i.id!=3,{tree:true});
Here is a demo Codepen
You could use an iterative approach with Array#some and call the callback iter recursive for the children. I found, splice.
function deleteItem(id) {
items.some(function iter(a, i, aa) {
if (a.id === id) {
aa.splice(i, 1);
return true;
}
return a.children.some(iter);
});
}
var items = [{ name: "a1", id: 1, children: [{ name: "a2", id: 2, children: [{ name: "a3", id: 3 }] }] }];
console.log(items);
deleteItem(3);
console.log(items);
deleteItem(2);
console.log(items);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use recursive function:
var items = [
{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3,
children: [{
name: "a4",
id: 4,
}]
}]
}]
}
];
function filterId(items, id) {
var len = items.length;
while (len--) {
if (items[len].id === id) {
items.splice(len, 1);
break;
} else {
filterId(items[len].children, id);
}
}
return items;
}
// filtering out the item with 'id' = 4
console.log(filterId(items, 4));
// filtering out the item with 'id' = 2
console.log(filterId(items, 2));

Categories

Resources