Json: edit deeply nested value - javascript

I have an array of json like this:
var tree = [
{
text: "Parent 1",
id: 1,
nodes: [
{
text: "Child 1",
id: 2,
nodes: [
{
text: "Grandchild 1"
id: 3,
},
{
text: "Grandchild 2"
id: 4,
nodes: [
{
text: "Grandchild 3"
id: 10,
},
{
text: "Grandchild 4"
id: 11,
nodes: [
{
text: "Grandchild 5"
id: 12,
},
{
text: "Grandchild 6"
id: 13,
}
]
}
]
}
]
},
{
text: "Child 2"
id: 5,
}
]
},
{
text: "Parent 2"
id: 6,
},
{
text: "Parent 3"
id: 7,
},
{
text: "Parent 4"
id: 8,
},
{
text: "Parent 5"
id: 9,
}
];
I'm trying to create a function that would take as parameter the tree, and id, and a newText parameter, that would find the node with the given id, replace the text by newText, and return the modified json.
Ex:
editTree(tree, 11, "Granchild 13435")
Is there a way to achieve this ?
I don't know how to solve this since I need the path to the key in order to edit the tree.

You can use recursive function for this.
var tree = [{"text":"Parent 1","id":1,"nodes":[{"text":"Child 1","id":2,"nodes":[{"text":"Grandchild 1","id":3},{"text":"Grandchild 2","id":4,"nodes":[{"text":"Grandchild 3","id":10},{"text":"Grandchild 4","id":11,"nodes":[{"text":"Grandchild 5","id":12},{"text":"Grandchild 6","id":13}]}]}]},{"text":"Child 2","id":5}]},{"text":"Parent 2","id":6},{"text":"Parent 3","id":7},{"text":"Parent 4","id":8},{"text":"Parent 5","id":9}]
function editTree(tree, id, val) {
for (var i in tree) {
if (i == 'id') {
if (tree[i] == id) {
tree.text = val
return 1;
}
}
if (typeof tree[i] == 'object') editTree(tree[i], id, val)
}
return tree;
}
console.log(editTree(tree, 11, "Granchild 13435"))

You could use an iterative and recursive approach for searching the node. If found stop iteration and return.
This proposal uses Array#some, which allowes to exit the iteration.
If a node from an actual node exists and the node is an array, then this node gets iterated.
function editTree(tree, id, text) {
tree.some(function iter(o) {
if (o.id === id) {
o.text = text;
return true;
}
return Array.isArray(o.nodes) && o.nodes.some(iter);
});
}
var tree = [{ text: "Parent 1", id: 1, nodes: [{ text: "Child 1", id: 2, nodes: [{ text: "Grandchild 1", id: 3, }, { text: "Grandchild 2", id: 4, nodes: [{ text: "Grandchild 3", id: 10, }, { text: "Grandchild 4", id: 11, nodes: [{ text: "Grandchild 5", id: 12, }, { text: "Grandchild 6", id: 13, }] }] }] }, { text: "Child 2", id: 5, }] }, { text: "Parent 2", id: 6, }, { text: "Parent 3", id: 7, }, { text: "Parent 4", id: 8, }, { text: "Parent 5", id: 9, }];
editTree(tree, 11, "Granchild 13435");
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Here is a simple function to find a node matching a property and a value :
function findNode(nodes, prop, value) {
if(!value || !(nodes instanceof Array)) return;
for(var i=0; i<nodes.length; i++) {
if(node = (value == nodes[i][prop]) ? nodes[i] : findNode(nodes[i]['nodes'], value)) {
return node;
}
}
}
Then simply call :
// Find node with id = 10
var node = findNode(tree, 'id', 10);
if(node) {
// Yeah! we found it, now change its text
node['text'] = 'Changed!';
}
// Ensure tree has been updated
console.log(tree);
Sample snippet (check out that text of node with id = 10 has changed) :
var tree=[{text:"Parent 1",id:1,nodes:[{text:"Child 1",id:2,nodes:[{text:"Grandchild 1",id:3},{text:"Grandchild 2",id:4,nodes:[{text:"Grandchild 3",id:10},{text:"Grandchild 4",id:11,nodes:[{text:"Grandchild 5",id:12},{text:"Grandchild 6",id:13}]}]}]},{text:"Child 2",id:5}]},{text:"Parent 2",id:6},{text:"Parent 3",id:7},{text:"Parent 4",id:8},{text:"Parent 5",id:9}];
function findNode(nodes, prop, value) {
if(!value || !(nodes instanceof Array)) return;
for(var i=0; i<nodes.length; i++) {
var node = (value == nodes[i][prop]) ? nodes[i] : findNode(nodes[i]['nodes'], prop, value);
if(node ) {
return node;
}
}
}
var node = findNode(tree, 'id', 10);
if(node) {
node['text'] = 'Changed!';
}
console.log(tree)

I've created library that uses recursive walk and one of its method is exactly what you need.
https://github.com/dominik791/obj-traverse
Use findFirst() method. The first parameter is a root object, not array, so you should create it at first:
var tree = {
text: 'rootObj',
nodes: [
{
text: 'Parent 1',
id: 1,
nodes: [
{
'text': 'Child 1',
id: 2,
nodes: [ ... ]
},
{
'name': 'Child 2',
id: 3,
nodes: [ ... ]
}
]
},
{
text: 'Parent2',
id: 6
}
};
Then:
var objToEdit = findFirst(tree, 'nodes', { id: 11 });
Now objToEdit is a reference to the object that you want to edit. So you can just:
objToEdit.text = 'Granchild 13435';
And your tree is updated.

Related

How to access nested key on object with array JavaScript?

I want to make hierarchy role layout. So I want to access nested JavaScript array with in a object. Needs to add 'row' and 'column' key in every object based depth and index.
API response array like this:
const dataArray = [
{
name: "master name",
child: [
{
name: "child 1a",
child: [
{ name: "child 1a2a" },
{
name: "child 1a2b",
child: [
{ name: "child 2b3a" },
{
name: "child 2b3b",
child: [
{ name: "child 3b4a" },
{ name: "child 3b4b" },
{ name: "child 3b4c" },
],
},
{ name: "child 2b3c" },
],
},
{ name: "child 1a2c" },
],
},
{
name: "child 1b",
child: [
{ name: "child 1b2a" },
{ name: "child 1b2b" },
{ name: "child 1b2c" },
],
},
],
},
];
This is what I have tried, row key added following code, need help for column
const assignDepth = (arr, row = 0, index = 0) => {
if (index < arr.length) {
arr[index].row = row;
if (arr[index].hasOwnProperty("child")) {
return assignDepth(arr[index].child, row + 1, 0);
}
return assignDepth(arr, row, index + 1);
}
return;
};
await assignDepth(dataArray);
console.log(JSON.stringify(dataArray, undefined, 4));
Then my expected result array is:
const resultArray = [
{
name: "master name",
row: 0,
column: 0,
child: [
{
name: "child 1a",
row: 1,
column: 1,
child: [
{ name: "child 1a2a", row: 2, column: 1 },
{
name: "child 1a2b",
row: 2,
column: 2,
child: [
{ name: "child 2b3a", row: 3, column: 1 },
{
name: "child 2b3b",
row: 3,
column: 2,
child: [
{ name: "child 3b4a", row: 4, column: 1 },
{ name: "child 3b4b", row: 4, column: 2 },
{ name: "child 3b4c", row: 4, column: 3 },
],
},
{ name: "child 2b3c", row: 3, column: 3 },
],
},
{ name: "child 1a2c", row: 2, column: 3 },
],
},
{
name: "child 1b",
row: 1,
column: 2,
child: [
{ name: "child 1b2a", row: 2, column: 4 },
{ name: "child 1b2b", row: 2, column: 5 },
{ name: "child 1b2c", row: 2, column: 6 },
],
},
],
},
];
So how can I render this, Anyone help me out. Thanks in advance!
You can try the following logic:
var ranking = {0:0};
let addRowColumn = (data,row=0) =>{
data.forEach(item=>{
item.row = row;
item.column = ranking[row];
ranking[row]+=1;
if(item.child && item.child.length){
ranking[row+1] = ranking[row+1] || 1;
addRowColumn(item.child,row+1);
}
});
};
addRowColumn(dataArray);
console.log(dataArray);
This is tested. you can change the ranking object to start with a specific row and column. The key of object represents the row and value represents the column of data.

I need to use javascript to turn a "flat" json object into a nested json tree with a 1 to many match ability

I need use javascript to turn a flat JSON object into a nested tree. The catch that I haven't yet seen in other solutions is that I need to have the ability to match 1 to many... Meaning that I sometimes have a child node that needs to fit into multiple parent nodes.
Here is my flat json object:
[{
id: 200,
title: "child with 2 parents",
parent_ids: [1, 2]
}, {
id: 202,
title: "child with 1 parent",
parent_ids: [1]
}, {
id: 1,
title: "parent 1",
children: []
}, {
id: 2,
title: "parent 2",
children: []
}]
I know that I can might be able to do something like loop through all the parents and children individually to match them up but that seems really inefficient and I'm wondering if there is a better way to go about this?
This is the ideal outcome
notice the child with 2 parents gets slotted into both parents
[
{
id: 1,
title: "parent 1",
children: [{
id: 200,
title: "child with 2 parents",
parent_ids: [1, 2]
}, {
id: 202,
title: "child with 1 parent",
parent_ids: [1]
}]
},
{
id: 2,
title: "parent 2",
children: [{
id: 200,
title: "child with 2 parents",
parent_ids: [1, 2]
}]
}
]
You could collect all nodes in a hash table and get all nodes without parents as result set.
function getTree(data) {
var temp = {},
parents = [];
data.forEach(o => {
o.children = temp[o.id] && temp[o.id].children;
temp[o.id] = o;
if (!o.parent_ids) {
parents.push(o.id);
return;
}
o.parent_ids.forEach(id => {
temp[id] = temp[id] || {};
temp[id].children = temp[id].children || [];
temp[id].children.push(o);
});
});
return parents.map(id => temp[id]);
}
var data = [{ id: 200, title: "child with 2 parents", parent_ids: [1, 2] }, { id: 202, title: "child with 1 parent", parent_ids: [1] }, { id: 1, title: "parent 1", children: [] }, { id: 2, title: "parent 2", children: [] }],
tree = getTree(data);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

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>');

Filter with treeview

I am experiencing problems using a treeview filter. I have made following method:
var tree =
[{
id: "Arvore",
text: "Arvore",
children: [
{
id: "Folha_1",
text: "Folha 1",
children: [
{
id: "Folha_1_1",
text: "Folha 1.1",
children: [
{
id: "dd",
text: "abc"
}
]
}
]
},
{
id: "Folha_2",
text: "Folha 2"
},
{
id: "Folha_3",
text: "Folha 3"
},
{
id: "Folha_4",
text: "Folha 4"
},
{
id: "Folha_5",
text: "Folha 5"
}
]
}];
filterData: function filterData(data, value) {
return data.filter(function(item) {
if (item.children) item.children = filterData(item.children, value);
return item.text.indexOf(value) > -1;
});
},
But when I enter text, for example Folha 1.1, I want it to return Arvore > Folha 1 > Folha 1.1, but the function returns only the first children. What can i do?
Something like this should work. If you want to return the path then you need to check if the item matches OR if it has children left (meaning a child matched).
var tree =
[{
id: "Arvore",
text: "Arvore",
children: [
{
id: "Folha_1",
text: "Folha 1",
children: [
{
id: "Folha_1_1",
text: "Folha 1.1",
children: [
{
id: "dd",
text: "abc"
}
]
}
]
},
{
id: "Folha_2",
text: "Folha 2"
},
{
id: "Folha_3",
text: "Folha 3"
},
{
id: "Folha_4",
text: "Folha 4"
},
{
id: "Folha_5",
text: "Folha 5"
}
]
}];
function filterData(data, value) {
return data.filter(function(item) {
if (item.children) item.children = filterData(item.children, value);
return item.text.indexOf(value) > -1 || (item.children && item.children.length > 0);
});
}
filterData(tree, "Folha 1.1")
console.log(tree)
I found solution:
filterData: function filterData(data, value, forceShow) {
return data.filter(function(item) {
if (item.children) item.children = filterData(item.children, value, item.text.indexOf(value) > -1);
return forceShow || (item.text.indexOf(value) > -1 || (item.children && item.children.length > 0));
});
},

Javascript Recursion on array and child arrays

Having a senior-moment, and struggling to get a recursive method to work correctly in Javascript.
There are similar Q&A's here, though nothing I see that has helped me so far.
That being said, if there is indeed a duplicate, i will remove this question.
Given the following array of objects:
var collection = [
{
id: 1,
name: "Parent 1",
children: [
{ id: 11, name: "Child 1", children: [] },
{ id: 12, name: "Child 2", children: [] }
]
},
{
id: 2,
name: "Parent 2",
children: [
{
id: 20,
name: "Child 1",
children: [
{ id: 21, name: "Grand Child 1", children: [] },
{ id: 22, name: "Grand Child 2", children: [] }
]
}
]
},
{
id: 3,
name: "Parent 3",
children: [
{ id: 31, name: "Child 1", children: [] },
{ id: 32, name: "Child 2", children: [] }
]
},
];
I've gone through a few attempts though my method seems to return early after going through one level only.
My latest attempt is:
Can someone please point me in the right direction.
function findType(col, id) {
for (i = 0; i < col.length; i++) {
if (col[i].id == id) {
return col[i];
}
if (col[i].children.length > 0) {
return findType(col[i].children, id);
}
}
return null;
}
I am trying to find an object where a given id matches, so looking for id 1 should return the whole object with the name Parent 1. If looking for id 31 then the whole object with the id 31 and name Child 1 should be returned.
This would translate into
var t = findType(collection, 1);
or
var t = findType(collection, 31);
Note I would like help with a pure JavaScript solution, and not a plugin or other library. Though they may be more stable, it won't help with the learning curve. Thanks.
You was close, you need a variable to store the temporary result of the nested call of find and if found, then break the loop by returning the found object.
Without, you return on any found children without iterating to the end of the array if not found at the first time.
function findType(col, id) {
var i, temp;
for (i = 0; i < col.length; i++) {
if (col[i].id == id) {
return col[i];
}
if (col[i].children.length > 0) {
temp = findType(col[i].children, id); // store result
if (temp) { // check
return temp; // return result
}
}
}
return null;
}
var collection = [{ id: 1, name: "Parent 1", children: [{ id: 11, name: "Child 1", children: [] }, { id: 12, name: "Child 2", children: [] }] }, { id: 2, name: "Parent 2", children: [{ id: 20, name: "Child 1", children: [{ id: 21, name: "Grand Child 1", children: [] }, { id: 22, name: "Grand Child 2", children: [] }] }] }, { id: 3, name: "Parent 3", children: [{ id: 31, name: "Child 1", children: [] }, { id: 32, name: "Child 2", children: [] }] }];
console.log(findType(collection, 31));
console.log(findType(collection, 1));
.as-console-wrapper { max-height: 100% !important; top: 0; }
const findType = (ar, id) => {
return ar.find(item => {
if (item.id === id) {
return item;
}
return item.children.find(cItem => cItem.id === id)
})
}
I think this suffice your needs
You need to ask for the "found" object
let found = findType(col[i].children, id);
if (found) {
return found;
}
Look at this code snippet
var collection = [{ id: 1, name: "Parent 1", children: [{ id: 11, name: "Child 1", children: [] }, { id: 12, name: "Child 2", children: [] } ] }, { id: 2, name: "Parent 2", children: [{ id: 20, name: "Child 1", children: [{ id: 21, name: "Grand Child 1", children: [] }, { id: 22, name: "Grand Child 2", children: [] } ] }] }, { id: 3, name: "Parent 3", children: [{ id: 31, name: "Child 1", children: [] }, { id: 32, name: "Child 2", children: [] } ] }];
function findType(col, id) {
for (let i = 0; i < col.length; i++) {
if (col[i].id == id) {
return col[i];
}
if (col[i].children.length > 0) {
let found = findType(col[i].children, id);
if (found) {
return found;
}
}
}
return null;
}
var t = findType(collection, 31);
console.log(t);
.as-console-wrapper {
max-height: 100% !important
}
1.
Actually your function findType return the value null for every id parameter at the node
{ id: 11, name: "Child 1", children: [] }
When you hit return, it stops all the recursion.
You have to check the return value from the nested call of findType function.
2.
Your for loop should looke like
for (let i = 0; i < col.length; i++)
instead of
for (i = 0; i < col.length; i++)
Because without the let you share a same variable i in the nested call of the function findType and the value will be changed for the dad calling function.
The function could be :
function findType(col, id) {
for (let i = 0; i < col.length; i++) {
if (col[i].id == id) {
return col[i];
}
var nested = findType(col[i].children, id);
if (nested) return nested;
}
return null;
}

Categories

Resources