I have parent-child JSON data and I want get all children (nested children) from selected parent.
For example, I have JSON data :
[{
"id": 1,
"parent": 0,
"name": "Parent"
}, {
"id": 2,
"parent": 1,
"name": "Child 1"
}, {
"id": 3,
"parent": 2,
"name": "Grand Child 1"
}, {
"id": 4,
"parent": 2,
"name": "Grand Child 2"
}, {
"id": 5,
"parent": 1,
"name": "Child 2"
}]
And I have function findAllChildren(1), where "1" is "parent" and then result of function should be :
[{
"id": 2,
"parent": 1,
"name": "Child 1"
}, {
"id": 3,
"parent": 2,
"name": "Grand Child 1"
}, {
"id": 4,
"parent": 2,
"name": "Grand Child 2"
}, {
"id": 5,
"parent": 1,
"name": "Child 2"
}]
And in other case, if i call findAllChildren(2), result of the function should like below :
[{
"id": 3,
"parent": 2,
"name": "Grand Child 1"
}, {
"id": 4,
"parent": 2,
"name": "Grand Child 2"
}]
What is the proper way to create function to solve that case? Thank you.
You can just iterate over the original data and look for items that has the specified id as parent_id. If found, do the same recursively with the element's id.
Check it out here: https://jsfiddle.net/6ydog1tj/2/
function findAllChildren (id, results, depth) {
for (d in data) {
if (data[d].parent == id) {
data[d].depth = depth
results.push(data[d])
findAllChildren(data[d].id, results, depth + 1)
}
}
}
var results = []
findAllChildren(1, results, 0)
$('body').append(results.map(function (element) { return Array(element.depth + 1).join(' -> ') + element.name + '<br>' }))
console.log(results)
prints out
Child 1
-> Grand Child 1
-> Grand Child 2
Child 2
I suggest to iterate all data and build a tree like object with properties to start the search with all given id.
Then the object is walked and the children iterated for the result.
function getDescendant(id) {
var result = [];
Array.isArray(object[id].children) && object[id].children.forEach(function iter(a) {
result.push({ id: a.id, parent: a.parent, name: a.name });
Array.isArray(a.children) && a.children.forEach(iter);
});
return result;
}
var data = [{ id: 1, parent: 0, name: "Parent" }, { id: 2, parent: 1, name: "Child 1" }, { id: 3, parent: 2, name: "Grand Child 1" }, { id: 4, parent: 2, name: "Grand Child 2" }, { id: 5, parent: 1, name: "Child 2" }],
object = function (data, root) {
var o = {};
data.forEach(function (a) {
a.children = o[a.id] && o[a.id].children;
o[a.id] = a;
o[a.parent] = o[a.parent] || {};
o[a.parent].children = o[a.parent].children || [];
o[a.parent].children.push(a);
});
return o;
}(data, 0);
console.log(getDescendant(1));
console.log(getDescendant(2));
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Array.prototype.filter to remove items from an array that do not match a predicate condition.
filter will loop over an array and run a function for each iteration. it the return value is true the item will be in the returned array.
The parentId function passed into filter is curried. it will lock in the parent id you are searching for in the scope and return the function that filter will run.
const data = [{
"id": 1,
"parent": 0,
"name": "Parent"
}, {
"id": 2,
"parent": 1,
"name": "Child 1"
}, {
"id": 3,
"parent": 2,
"name": "Grand Child 1"
}, {
"id": 4,
"parent": 2,
"name": "Grand Child 2"
}, {
"id": 5,
"parent": 1,
"name": "Child 2"
}]
function parentId(id) {
return function(item) {
return item.parent === id
}
}
console.log(
data.filter(parentId(2))
)
Related
Today I was working on a problem, which states as follows:
Problem:
INPUT: [{..}, {..}, ..] Array of objects;
Each object is has {"id": required, "children": []}
The objects has parent-child relation based on "id" and "children" props
OUTPUT: [{..}, {..}, ..] Array in a tree (hierarchy) order :multi-level.
Input:
[{
"id": 1,
"name": "Earth",
"children": [2, 3]
}, {
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [4]
}, {
"id": 4,
"name": "Germany",
"children": [5]
}, {
"id": 5,
"name": "Hamburg",
"children": []
}]
OutPut
[{
"id": 1,
"name": "Earth",
"children": [{
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [{
"id": 4,
"name": "Germany",
"children": [{
"id": 5,
"name": "Hamburg",
"children": []
}]
}]
}]
}]
My approach
I decided to solve this by iterating through each element in the array and recursively find and append objects to children of each element.
So just to start with, I decided to have only First level children appended their respective parents. And my code is following.
var posts = [{"id":1,"name":"Earth","children":[2,3]},{"id":2,"name":"Asia","children":[]},{"id":3,"name":"Europe","children":[4]},{"id":4,"name":"Germany","children":[5]},{"id":5,"name":"Hamburg","children":[]}]
function getElementById (id, posts) {
for(var i =0; i< posts.length; i++){
if(posts[i].id === id){
var found = posts[i];
///// FUN here -> //// posts.splice(i, 1);
return found;
}
}
}
function refactorChildren(element, posts) {
if(!element.children || element.children.length === 0) {
return element;
}
var children = [];
for(var i = 0; i < element.children.length; i++){
var childElement = getElementById(element.children[i], posts);
children.push(childElement);
}
element.children = children;
return element;
}
function iterate(posts) {
var newPosts = [];
var des = [...posts]
for(var i = 0; i < des.length; i++){
var childedElement = refactorChildren(des[i], des);
newPosts.push(childedElement);
}
return newPosts;
}
var filtered = iterate(posts);
console.log(JSON.stringify(filtered))
Surprisingly above code Solves the ACTUAL PROBLEM (except a lil bit of more work)
My Expected Result should be the following: Array of objects with only First level children
[{
"id": 1,
"name": "Earth",
"children": [{
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [4]
}]
}, {
"id": 4,
"name": "Germany",
"children": [{
"id": 5,
"name": "Hamburg",
"children": []
}]
}]
And I do get the above result if I uncomment the ///// FUN here -> //// line. Which is erasing the iterating object on the go.
So my problem is
I want to know - HOW DID? All the objects got appended correctly to their respective Parent objects by that code? My next step was to add a recursion call to the function refactorChildren(with-childElement).
AND
How did, just by adding posts.splice(i, 1); got me MY expected result from the code?
Please help me understand, I just cant go ahead without knowing "HOW".
Thanks
While traversing the objects, you recursively call a function on all its chilfren and remove the objects from the array:
[
{ id: 1, children: [2], }, // < iterator
{ id: 2, children: [] }, // < gets spliced out recursively
]
If a child is in the array before its parent however, this won't work as you copy the child into another array before the parent gets visited.
Maybe you are interested in a different approach with only a single loop for getting the parent elements and their children.
This works for unsorted data, too.
var data = [{ id: 1, name: "Earth", children: [2, 3] }, { id: 2, name: "Asia", children: [] }, { id: 3, name: "Europe", children: [4] }, { id: 4, name: "Germany", children: [5] }, { id: 5, name: "Hamburg", children: [] }],
tree = function (array) {
var r = {},
children = new Set,
result = [];
array.forEach(o => {
Object.assign(
r[o.id] = r[o.id] || {},
o,
{ children: o.children.map(id => (children.add(id), r[id] = r[id] || {})) }
);
});
return Object.values(r).filter(({ id }) => !children.has(id));
}(data);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have this categories tree input :
"categories": [
{
"text": "Upstate",
"id": 3,
"category_parent_id": 0,
"children": []
},
{
"text": "North",
"id": 2,
"category_parent_id": 0,
"children": [
{
"text": "Child N 1",
"id": 5,
"category_parent_id": 2,
"children": [
{
"text": "Another Child 1",
"id": 11,
"category_parent_id": 5,
"children": []
},
{
"text": "Another Child 2",
"id": 10,
"category_parent_id": 5,
"children": []
}
]
},
{
"text": "Activity",
"id": 4,
"category_parent_id": 2,
"children": []
}
]
},
{
"text": "Health",
"id": 1,
"category_parent_id": 0,
"children": [
{
"text": "Good Health",
"id": 9,
"category_parent_id": 1,
"children": []
},
{
"text": "Bad Health",
"id": 8,
"category_parent_id": 1,
"children": []
}
]
}
]
So, now I want to populate my select box like this :
Upstate
North
-Child N 1
--Another Child 1
--Another Child 2
-Activity
Health
-Good Health
-Bad Health
So, how can I parse through the input tree and populate the select box with these values? Any algorithm or recursive function approach I can use to achieve this ?
make a recursive function
flatCategories(data: any[], children: any[], index: number) {
data=data||[];
children.forEach(x => {
data.push({ id: x.id, text: '-'.repeat(index) + x.text });
if (x.children && x.children.length)
this.flatCategories(data, x.children, index + 1)
})
return data
}
You can use like
let dataFlat=this.flatCategories([], this.data.categories, 0);
console.log(this.dataflat.map(x => x.text))
If you want to create a recursive component it's easy (but in case of select not work)
#Component({
selector: 'item-line',
template: `
<div *ngFor="let item of children" [style.margin-left]="index+'rem'">
{{item.text}}
<item-line *ngIf="item.children" [children]="item.children" [index]="index+1">
</item-line>
</div>
`,
})
export class HelloComponent {
#Input() children:any[]
#Input() index:number=0;
}
You can see in stackblitz
Find the bellow parent child tree object I have created. I need to find the root parent of a given child id. For example child id - 242 root parent id is 238. There are similar questions have been asked and this is the one I found very similar to my question.
Convert parent-child array to tree
I have change the original code a bit But not working for child element. The issue is here. when it comes the recursive function with rootNode.children for loop will not execute since it does not loop through children. But if I change the for loop for (var i = 0; i < rootNode.length; i++) to for (var i = 0; i < rootNode.children.length; i++) then it break on first loop since does not have children. I'm sure with small code change this can make work.
var getParent = function (rootNode, rootId) {
if (rootNode.id === rootId)
return rootNode;
//for (var i = 0; i < rootNode.children.length; i++) -- original code line not working first time
for (var i = 0; i < rootNode.length; i++) {
var child = rootNode[i];
if (child.id === rootId)
return child;
if (typeof child.children !== 'undefined')
var childResult = getParent(child, rootId);
if (childResult != null) return childResult;
}
return null;
};
var mytree = [
{
"id": 245,
"parent": "0",
"title": "project1",
"children": [
{
"id": 246,
"parent": "245",
"title": "sub task 1"
}
]
},
{
"id": 238,
"parent": "0",
"title": "project2",
"children": [
{
"id": 240,
"parent": "238",
"title": "sub task 2"
},
{
"id": 242,
"parent": "238",
"title": "sub task 3",
"children" : [
{
"id": 241,
"parent": "242",
"title": "sub task 3.1"
}
]
}
]
},
{
"id": 173,
"parent": "0",
"title": "project3"
}
];
console.log(JSON.stringify(getParent(mytree, 238)['title']));
console.log(JSON.stringify(getParent(mytree, 241)));
You need to iterate the given root node, because this is an array, not an object.
function getParent(root, id) {
var node;
root.some(function (n) {
if (n.id === id) {
return node = n;
}
if (n.children) {
return node = getParent(n.children, id);
}
});
return node || null;
}
var mytree = [{ id: 245, parent: "0", title: "project1", children: [{ id: 246, parent: "245", title: "sub task 1" }] }, { id: 238, parent: "0", title: "project2", children: [{ id: 240, parent: "238", title: "sub task 2" }, { id: 242, parent: "238", title: "sub task 3", children: [{ id: 241, parent: "242", title: "sub task 3.1" }] }] }, { id: 173, parent: "0", title: "project3" }];
console.log(getParent(mytree, 238));
console.log(getParent(mytree, 241));
.as-console-wrapper { max-height: 100% !important; top: 0; }
More a classical attempt
function getParent(root, id) {
var i, node;
for (var i = 0; i < root.length; i++) {
node = root[i];
if (node.id === id || node.children && (node = getParent(node.children, id))) {
return node;
}
}
return null;
}
var mytree = [{ id: 245, parent: "0", title: "project1", children: [{ id: 246, parent: "245", title: "sub task 1" }] }, { id: 238, parent: "0", title: "project2", children: [{ id: 240, parent: "238", title: "sub task 2" }, { id: 242, parent: "238", title: "sub task 3", children: [{ id: 241, parent: "242", title: "sub task 3.1" }] }] }, { id: 173, parent: "0", title: "project3" }];
console.log(getParent(mytree, 238));
console.log(getParent(mytree, 241));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think my js tools can help you
https://github.com/wm123450405/linqjs
var mytree = [
{
"id": 245,
"parent": "0",
"title": "project1",
"children": [
{
"id": 246,
"parent": "245",
"title": "sub task 1"
}
]
},
{
"id": 238,
"parent": "0",
"title": "project2",
"children": [
{
"id": 240,
"parent": "238",
"title": "sub task 2"
},
{
"id": 242,
"parent": "238",
"title": "sub task 3",
"children" : [
{
"id": 241,
"parent": "242",
"title": "sub task 3.1"
}
]
}
]
},
{
"id": 173,
"parent": "0",
"title": "project3"
}
];
//node.asEnumerable get a Tree object
//isAncestorOf to predicate the root node is or not the ancestor node of parameter
console.log(mytree.find(node => node.asEnumerable(node => node.children, node => node.id).isAncestorOf(241)));
console.log(mytree.find(node => node.asEnumerable(node => node.children, node => node.id).isAncestorOf(242)).title);
<script src="https://wm123450405.github.io/linqjs/libs/linq-js.min.js"></script>
Suppose I have a tree of objects like the following, perhaps created using the excellent algorithm found here: https://stackoverflow.com/a/22367819/3123195
{
"children": [{
"id": 1,
"title": "home",
"parent": null,
"children": []
}, {
"id": 2,
"title": "about",
"parent": null,
"children": [{
"id": 3,
"title": "team",
"parent": 2,
"children": []
}, {
"id": 4,
"title": "company",
"parent": 2,
"children": []
}]
}]
}
(Specifically in this example, the array returned by that function is nested as the children array property inside an otherwise empty object.)
How would I convert it back to a flat array?
Hope your are familiar with es6:
let flatten = (children, extractChildren) => Array.prototype.concat.apply(
children,
children.map(x => flatten(extractChildren(x) || [], extractChildren))
);
let extractChildren = x => x.children;
let flat = flatten(extractChildren(treeStructure), extractChildren)
.map(x => delete x.children && x);
UPD:
Sorry, haven't noticed that you need to set parent and level. Please find the new function below:
let flatten = (children, getChildren, level, parent) => Array.prototype.concat.apply(
children.map(x => ({ ...x, level: level || 1, parent: parent || null })),
children.map(x => flatten(getChildren(x) || [], getChildren, (level || 1) + 1, x.id))
);
https://jsbin.com/socono/edit?js,console
This function will do the job, plus it adds a level indicator to each object. Immediate children of treeObj will be level 1, their children will be level 2, etc. The parent properties are updated as well.
function flatten(treeObj, idAttr, parentAttr, childrenAttr, levelAttr) {
if (!idAttr) idAttr = 'id';
if (!parentAttr) parentAttr = 'parent';
if (!childrenAttr) childrenAttr = 'children';
if (!levelAttr) levelAttr = 'level';
function flattenChild(childObj, parentId, level) {
var array = [];
var childCopy = angular.extend({}, childObj);
childCopy[levelAttr] = level;
childCopy[parentAttr] = parentId;
delete childCopy[childrenAttr];
array.push(childCopy);
array = array.concat(processChildren(childObj, level));
return array;
};
function processChildren(obj, level) {
if (!level) level = 0;
var array = [];
obj[childrenAttr].forEach(function(childObj) {
array = array.concat(flattenChild(childObj, obj[idAttr], level+1));
});
return array;
};
var result = processChildren(treeObj);
return result;
};
This solution takes advantage of Angular's angular.extend() function to perform a copy of the child object. Wiring this up with any other library's equivalent method or a native function should be a trivial change.
The output given for the above example would be:
[{
"id": 1,
"title": "home",
"parent": null,
"level": 1
}, {
"id": 2,
"title": "about",
"parent": null,
"level": 1
}, {
"id": 3,
"title": "team",
"parent": 2,
"level": 2
}, {
"id": 4,
"title": "company",
"parent": 2,
"level": 2
}]
It is also worth noting that this function does not guarantee the array will be ordered by id; it will be based on the order in which the individual objects were encountered during the operation.
Fiddle!
Here it goes my contribution:
function flatNestedList(nestedList, childrenName, parentPropertyName, idName, newFlatList, parentId) {
if (newFlatList.length === 0)
newFlatList = [];
$.each(nestedList, function (i, item) {
item[parentPropertyName] = parentId;
newFlatList.push(item);
if (item[childrenName] && item[childrenName].length > 0) {
//each level
flatNestedList(item[childrenName], childrenName, parentPropertyName, idName, newFlatList, item[idName]);
}
});
for (var i in newFlatList)
delete (newFlatList[i][childrenName]);
}
Try following this only assumes each item is having children property
class TreeStructureHelper {
public toArray(nodes: any[], arr: any[]) {
if (!nodes) {
return [];
}
if (!arr) {
arr = [];
}
for (var i = 0; i < nodes.length; i++) {
arr.push(nodes[i]);
this.toArray(nodes[i].children, arr);
}
return arr;
}
}
Usage
let treeNode =
{
children: [{
id: 1,
title: "home",
parent: null,
children: []
}, {
id: 2,
title: "about",
parent: null,
children: [{
id: 3,
title: "team",
parent: 2,
children: []
}, {
id: 4,
title: "company",
parent: 2,
children: []
}]
}]
};
let flattenArray = _treeStructureHelper.toArray([treeNode], []);
This is data:
const data = {
id: '1',
children: [
{
id: '2',
children: [
{
id: '4',
children: [
{
id: '5'
},
{
id: '6'
}
]
},
{
id: '7'
}
]
},
{
id: '3',
children: [
{
id: '8'
},
{
id: '9'
}
]
}
]
}
In React.JS just declare an array field in state and push items to that array.
const getAllItemsPerChildren = item => {
array.push(item);
if (item.children) {
return item.children.map(i => getAllItemsPerChildren(i));
}
}
After function call your array in state will hold all items as below:
One more 😄😁
function flatten(root, parent=null, depth=0, key='id', flat=[], pick=() => {}) {
flat.push({
parent,
[key]: root[key],
depth: depth++,
...pick(root, parent, depth, key, flat)
});
if(Array.isArray(root.children)) {
root.children.forEach(child => flatten(child, root[key], depth, key, flat, pick));
}
}
let sample = {
"id": 0,
"children": [{
"id": 1,
"title": "home",
"parent": null,
"children": []
}, {
"id": 2,
"title": "about",
"parent": null,
"children": [{
"id": 3,
"title": "team",
"parent": 2,
"children": []
}, {
"id": 4,
"title": "company",
"parent": 2,
"children": []
}]
}]
};
let flat = [];
flatten(sample, null, 0, 'id', flat, root => ({ title: root.title }));
let expected = [
{
"id": 0,
"parent": null,
"depth": 0
},
{
"id": 1,
"parent": 0,
"depth": 1,
"title": "home"
},
{
"id": 2,
"parent": 0,
"depth": 1,
"title": "about"
},
{
"id": 3,
"parent": 2,
"depth": 2,
"title": "team"
},
{
"id": 4,
"parent": 2,
"depth": 2,
"title": "company"
}
];
I have 2 JSON arrays, both of which can contain nested arrays:
var serverArr = [
{ "id": 1, "text": "Item 1" },
{ "id": 2, "text": "Item 2" },
{ "id": 3, "text": "Item 3", "children": [{ "id": 20, "text": "Item 20" },
{ "id": 21, "text": "Item 21" }] },
{ "id": 4, "text": "Item 4" }
];
var userArr = [
{ "id": 1, "text": "Item 1" },
{ "id": 3, "text": "Item 3", "children": [{ "id": 20, "text": "Item 20" },
{ "id": 25, "text": "Item 25" }] },
{ "id": 5, "text": "Item 5" }
];
What I need to do is combine them into 1 array, only taking the matching values. So the result should look like:
[{ "id": 1, "text": "Item 1" },
{ "id": 3, "text": "Item 3", "children": [{ "id": 20, "text": "Item 20" }]}];
I'm using the results with the jsTree plugin, so the format has to be like this unfortunately.
How can I get the end result from those 2 arrays?
I have defined a function that does 3 things to solve this problem
First thing it gets all the elements that are in both arrays and it collects all childs for every item in both arrays
Second thing is filtering all the childs elements to keep only unique ones
Third thing is changing the children of the parent elements to keep only unique ones
function combine(array1,array2)
{
result = serverArr.concat(userArr);
var childs = [];
// First step
result = result.filter(function(elem, index, self)
{
if(elem.children != undefined)
childs[elem.id] = ((childs[elem.id] == undefined) ? [] : childs[elem.id]).concat(elem.children);
for (key in self)
if(key < index && elem.id == self[key].id )
return true;
return false;
});
// Second step
for(i in childs)
childs[i] = childs[i].filter(function(elem, index, self)
{
for (key in self)
if(key < index && JSON.stringify(elem) == JSON.stringify(self[key]) )
return true;
return false;
});
//Third step
for(key in result)
if(childs[result[key].id] != undefined)
result[key].children = childs[result[key].id];
return result;
}