looping through a json tree object and create new array - javascript

Is there a way good way JS/ES6 to loop through an object and it's children and creating new object tree array.
I have this json tree object:
[
{
id: "001",
deparmentsIds: [
"002",
"003"
],
details: {
parentDeparmentsId: null,
name: "Top"
}
},
{
id: "002",
deparmentsIds:[
"004"
],
details: {
parentDeparmentsId: ["001"],
name: "Operations"
}
},
{
id: "003",
deparmentsIds:[]
details: {
parentDeparmentsId: ["001"],
name: "Support"
}
},
{
id: "004",
deparmentsIds:[]
details: {
parentDeparmentsId: ["002"],
name: "Support operations"
}
}
]
I want to create new object array tree that looks like this:

You could create recursive function with reduce and map method to create nested object structure.
const data = [{"id":"001","deparmentsIds":["002","003"],"details":{"parentDeparmentsId":null,"name":"Top"}},{"id":"002","deparmentsIds":["004"],"details":{"parentDeparmentsId":"001","name":"Operations"}},{"id":"003","deparmentsIds":[],"details":{"parentDeparmentsId":"001","name":"Support"}},{"id":"004","deparmentsIds":[],"details":{"parentDeparmentsId":"002","name":"Support operations"}}]
function tree(input, parentId) {
return input.reduce((r, e) => {
if (e.id == parentId || parentId == undefined && e.details.parentDeparmentsId == null) {
const children = [].concat(...e.deparmentsIds.map(id => tree(input, id)))
const obj = {
[e.details.name]: children
}
r.push(obj)
}
return r;
}, [])
}
const result = tree(data)
console.log(result)

You could collect all information in an object with a single loop and return only the nodes with no parent.
function getTree(data, root) {
var o = {};
data.forEach(({ id, details: { parentDeparmentsId: parent, name } }) => {
var temp = { id, name };
if (o[id] && o[id].children) {
temp.children = o[id].children;
}
o[id] = temp;
o[parent] = o[parent] || {};
o[parent].children = o[parent].children || [];
o[parent].children.push(temp);
});
return o[root].children;
}
var data = [{ id: "001", deparmentsIds: ["002", "003"], details: { parentDeparmentsId: null, name: "Top" } }, { id: "002", deparmentsIds: ["004"], details: { parentDeparmentsId: ["001"], name: "Operations" } }, { id: "003", deparmentsIds: [], details: { parentDeparmentsId: ["001"], name: "Support" } }, { id: "004", deparmentsIds: [], details: { parentDeparmentsId: ["002"], name: "Support operations" } }],
tree = getTree(data, null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Filter with different levels of depth

I have a menu with this structure
item1
item2
childrenOfItem2
childrenOfchildren1
childrenOfchildren2
HELLOchildrenOfchildren3
childrenOfItem2
childrenOfItem2
HELLOitem3
item4
childrenOfItem4
HELLOchildrenOfItem4
item5
childrenOfItem5
So, Id' like to get all the items that have the word "HELLO" and what I'm doing is a loop over the first items, then, another loop and then another loop, is there any other way of doing it? Since let's say that if we add another level of depth in the menu it will not work,
Thank you!
Edited: adding JS for better understanding
const matchName = (item, word) =>
item?.title?.toLowerCase()?.includes(word?.toLowerCase());
const filter = (word = "", arr = []) => {
const listOfItems = [];
arr.forEach((item) => {
if (matchName(item, word)) {
listOfItems.push(item);
} else if (item?.children?.length > 0) {
const newSubItem = [];
item.children.forEach((subItem) => {
if (matchName(subItem, word)) {
newSubItem.push(subItem);
} else if (subItem?.children?.length > 0) {
const newSubSubItems = [];
subItem.children.forEach((subsubItem) => {
if (matchName(subsubItem, word)) {
newSubSubItems.push(subsubItem);
}
});
if (newSubSubItems?.length > 0) {
newSubItem.push({ ...subItem, children: newSubSubItems });
}
}
});
if (newSubItem?.length > 0) {
listOfItems.push({ ...item, children: newSubItem });
}
}
});
return listOfItems;
};
Sample of arr received as parameter in the fnc:
const list = [
{
id: "41",
title: "sample",
children: [
{
id: "42",
title: "sample",
children: [
{
id: "43",
title: "sample",
children: [],
},
{
id: "44",
title: "sample",
children: [],
},
{
id: "45",
title: "sample",
children: [],
},
],
},
{
id: "46",
title: "sample",
children: [
{
id: "47",
title: "sample",
children: [],
},
{
id: "48",
title: "sample",
children: [],
},
],
},
],
},
{
id: "29",
title: "sample",
children: [
{
id: "30",
title: "sample",
children: [],
},
{
id: "49",
title: "sample",
children: [],
},
{
id: "31",
title: "sample",
children: [],
},
],
},
];
If you really don't want to change your structure and flatten your list, you can loop them dynamically, just use a function and call it within itself.
Here's an example using the const list you provided:
let found = false,
loop = (items, filter) => {
found = false;
let results = items.filter(a => filter(a));
if(results.length > 0) {
found = true;
return results;
}
if(!found && items && items.length > 0) {
for(let i = 0; i < items.length && !found; i++) {
if(items[i] && items[i].children && items[i].children.length > 0)
results = loop(items[i].children, filter);
}
}
return results;
};
let items = loop(list, item => item.id && item.id == "48");
In the example above we filter the list dynamically, iterating each and every item in the list and return the first item we find that matches a provided filter (2nd parameter).
You can use this to do pretty much whatever you'd like and can add arguments to add the menu "level" you're currently on, the parents, etc.
Note that this might get a bit slow if the list goes very deep, wrote it quickly and didn't tested outside of your provided list.
Ideally I would change the structure in order to make it easier to work with but if you must keep it this way, this works.
You could find the object (without children) and get a flat array.
const
find = (array, title) => array.flatMap(({ children, ...o }) => [
...(o.title.includes(title) ? [o]: []),
...find(children, title)
]),
list = [{ id: "41", title: "sample", children: [{ id: "42", title: "sample", children: [{ id: "43", title: "sample", children: [] }, { id: "44", title: "sample", children: [] }, { id: "45", title: "sample", children: [] }] }, { id: "46", title: "no sample", children: [{ id: "47", title: "sample", children: [] }, { id: "48", title: "sample", children: [] }] }] }, { id: "29", title: "sample", children: [{ id: "30", title: "sample", children: [] }, { id: "49", title: "no sample", children: [] }, { id: "31", title: "sample", children: [] }] }];
console.log(find(list, 'no sample'));
console.log(find(list, 'ample'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

how to find object using recursion in javascript?

let tree = {
name: "A",
children: [
{
name: 'A-1',
children: [
{name: "A-1-A"},
{name: "A-1-B"},
]
},
{
name: 'B-1',
children: [
{
name: "B-1-A",
children: [
{name: "B-11-A"},
{name: "B-11-B"}
]
},
{name: "B-1-B"},
]
},
]
};
I am trying to find object from tree object using recursion .
when I call like this searchFn(tree,'A-1') it should return {
name: 'A-1',
children: [
{name: "A-1-A"},
{name: "A-1-B"},
]
} object
it I call like this searchFn(tree,'A-1-A') it should return this
{name: "A-1-A"}
I tried like this but not working
function searchFn(obj ,searchText){
if(obj.name === searchText) return obj
if(obj.children.length > 0){
return searchFn(obj.children.pop(),searchText)
}
return null
}
You need to iterate the children of the object and take a variable for the result.
function searchFn(object, searchText) {
var result;
if (object.name === searchText) return object;
(object.children || []).some(o => result = searchFn(o, searchText));
return result || null;
}
let tree = { name: "A", children: [{ name: 'A-1', children: [{ name: "A-1-A" }, { name: "A-1-B" }] }, { name: 'B-1', children: [{ name: "B-1-A", children: [{ name: "B-11-A" }, { name: "B-11-B" }] }, { name: "B-1-B" }] }] };
console.log(searchFn(tree, 'foo'));
console.log(searchFn(tree, 'A-1'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
First, you should create a class for readability. Then you should have a loop inside the function that iterates over the children and only returns if a child is found.
class Node {
static fromStruct = (struct) => {
const tree = new Node(struct.name);
if(!struct.children) return tree;
for(const child of struct.children) {
tree.children.push(Node.fromStruct(child));
}
return tree;
}
constructor(name){
this.name = name;
this.children = [];
}
addChild = (parentName, childName) => {
const parent = this.searchFn(parentName);
if(!parent) throw new Error("Parent not found");
parent.children.push(new Node(childName));
}
searchFn = (name) => {
if(this.name === name) return this;
for(const child of this.children) {
const found = child.searchFn(name);
if(found !== null) return found;
}
return null;
}
}
const data = {
name: "A",
children: [
{
name: 'A-1',
children: [
{name: "A-1-A"},
{name: "A-1-B"},
]
},
{
name: 'B-1',
children: [
{
name: "B-1-A",
children: [
{name: "B-11-A"},
{name: "B-11-B"}
]
},
{name: "B-1-B"},
]
},
]
};
const tree = Node.fromStruct(data);
console.log(tree.searchFn("A-1-A"));
const tree = {"name":"A","children":[{"name":"A-1","children":[{"name":"A-1-A"},{"name":"A-1-B"}]},{"name":"B-1","children":[{"name":"B-1-A","children":[{"name":"B-11-A"},{"name":"B-11-B"}]},{"name":"B-1-B"}]}]}
const searchFn = (tree, name) => {
let result = null;
if (typeof tree !== "object") return result;
if (tree.name === name) return tree;
if (tree.children && tree.children.length) {
tree.children.some(data => (result = searchFn(data, name)));
}
return result;
};
console.log(searchFn(tree, "A-1-A"));
console.log(searchFn(tree, "A-1"));
console.log(searchFn(tree, ""));
.as-console-row {color: blue!important}
You can try something like this
let tree = { name: "A", children: [{ name: 'A-1', children: [{ name: "A-1-A" }, { name: "A-1-B" }] }, { name: 'B-1', children: [{ name: "B-1-A", children: [{ name: "B-11-A" }, { name: "B-11-B" }] }, { name: "B-1-B" }] }] };
function searchFn(obj, text){
if(obj.name === text) return obj
else{
if(obj && obj.children && Array.isArray(obj.children)){
for(let value of obj.children){
if(searchFn(value, text)){
return searchFn(value, text)
}
}
}
}
return null
}
console.log(searchFn(tree, 'A-1'))
console.log(searchFn(tree, 'A-1-A'))
console.log(searchFn(tree, 'A-A-A'))
The problem with your function is that it only checks for the popped element from the array in the children property of the passed object, and not the other elements. Try this:
function searchFn(obj ,searchText) {
if(obj.name === searchText) return obj;
if(obj.children) {
for (let x of obj.children) {
let y = searchFn(x,searchText);
if (y)
return y;
}
}
return null;
}

Recursive Filter on Nested array

I need to filter a nested structure, that looks like this, based on query. I need to return all objects, including the parent object of the subtree, which match the query string in object name. Please help, i am stuck.
[
{
name: 'bob',
type: 1,
children: [
{
name: 'bob',
type: 2,
children: [
{
name: 'mike',
type: 3,
children: [
{
name:'bob',
type: 7,
children: []
},
{
name: 'mike',
type: 9,
children: []
}
]
}
]
},
{
name: 'mike',
type: 2
}
]
}
]
Right now i am able to find a match in the tree recursively, but the function returns the object on the first match and does not search deeper on sub levels in the same object. Any suggestions, how i can modify the code to go search through all levels recursively?
return tree.map(copy).filter(function filterNested(node) {
if (node.name.toLowerCase().indexOf(query) !== -1) {
return true;
}
if (node.children) {
return (node.children = node.children.map(copy).filter(filterNested))
.length;
}
});
if I am searching for query 'bob', the expected result should be,
const arr = [
{
name: 'bob',
type: 1,
children: [
{
name: 'bob',
type: 2,
children: [
{
name: 'mike',
type: 3,
children: [
{
name:'bob',
type: 7
},
]
}
]
},
]
}
]
You could reduce the array and build new objects with optional children, if they have a length not zero.
function filter(array, fn) {
return array.reduce((r, o) => {
var children = filter(o.children || [], fn);
if (fn(o) || children.length) r.push(Object.assign({}, o, children.length && { children }));
return r;
}, []);
}
var data = [{ name: 'bob', type: 1, children: [{ name: 'bob', type: 2, children: [{ name: 'mike', type: 3, children: [{ name: 'bob', type: 7 }, { name: 'same', typ: 9 }] }] }, { name: 'mike', type: 2 }] }],
result = filter(data, ({ name }) => name.toLowerCase() === 'bob');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Something like this, perhaps?
function nestedFilter(query, nodes) {
return nodes.reduce((result, node) => {
const filteredChildren = node.children && nestedFilter(query, node.children);
const nameMatches = node.name.toLowerCase().indexOf(query) !== -1;
const childrenMatch = filteredChildren && filteredChildren.length;
const shouldKeep = nameMatches || childrenMatch;
return shouldKeep
? result.concat({ ...node, children: filteredChildren })
: result;
}, []);
}
You can use a recursive reduce for this, where you check if there are any children or if the name matches before you accumulate the object:
const example = [{
name: 'bob',
type: 1,
children: [{
name: 'bob',
type: 2,
children: [{
name: 'mike',
type: 3,
children: [{
name: 'bob',
type: 7,
children: []
},
{
name: 'mike',
type: 9,
children: []
}
]
}]
},
{
name: 'mike',
type: 2
}
]
}];
function reduceName(accum, item, matcher) {
item.children = (item.children || []).reduce((a,i)=>reduceName(a,i,matcher),[]);
if (!item.children.length) delete item.children;
if (matcher(item) || item.children) accum.push(item);
return accum;
}
console.log(example.reduce((a,i)=>reduceName(a,i,x=>x.name==='bob'),[]));
I would just use good old visitor pattern to traverse and build new tree.
class Visitor {
constructor(predicate) {
this.predicate = predicate;
}
visit(item) {
if (Array.isArray(item)) {
return this.visitArray(item);
} else if (item) {
return this.visitItem(item);
}
}
visitArray(item) {
const result = [];
for (let e of item) {
const item = this.visit(e);
if (item) {
result.push(item);
}
}
return result;
}
visitItem(item) {
const children = this.visit(item.children);
const hasChildren = (children && children.length > 0);
if (hasChildren || this.predicate(item)) {
return {
name: item.name,
type: item.type,
children: children
}
}
return null;
}
}
const visitor = new Visitor((item) => item.name === "bob");
const result = visitor.visit(data);
console.log(result);
A wonderful time to learn about mutual recursion and continuation passing style
const data =
[{name:'bob',type:1,children:[{name:'bob',type:2,children:[{name:'mike',type:3,children:[ {name:'bob',type:7,children:[]},{name:'mike',type:9,children:[]}]}]},{name:'mike',type:2}]}]
const identity = x => x
const search = (all = [], query = identity, pass = identity) =>
all.flatMap(v => search1(v, query, pass))
const search1 = (one = {}, query = identity, pass = identity) =>
query(one)
? pass([ { ...one, children: search(one.children, query) } ])
: search
( one.children
, query
, children =>
children.length === 0
? pass([])
: pass([ { ...one, children } ])
)
const result =
search(data, x => x.name === "bob")
console.log(JSON.stringify(result, null, 2))
I do think that this question will always be relevant so here is how I did it !
After a few hours ;)
var res = yourArray.filter(function f(el) {
if (el.children.length > 0) {
el.children = el.childreb.filter(f);
}
if (el.name === "bob") return true; // you can put the condition you need here.
})
//res is your returned array
Trully hope this code will benefit some of youl :)

Javascript Recurse JSON array with children objects

Is there a way to recurse the following JSON without for-looping the nested children?
My recursive function must be missing a case as it's not returning everything.
iterateTree(node, children) {
console.log(node.name)
if(node.children.length == 0) {
return;
}
for(var i = 0; i < children.length; i++) {
var child_node = children[i];
return this.iterateTree(child_node, child_node.children);
}
}
for(var i = 0; i < sample.length; i++) {
var node = sample[i];
this.iterateTree(node, node.children);
}
var sample = [
{
"name": "hello world",
"children": [
{
"name": "fruits",
"children": []
},
{
"name": "vegetables",
"children": []
},
{
"name": "meats",
"children": [
{
"name": "pork",
"children": []
},
{
"name": "beef",
"children": []
},
{
"name": "chicken",
"children": [
{
"name": "organic",
"children": []
},
{
"name": "farm raised",
"children": []
}
]
},
]
}
]
},
{
"name": "second folder",
"children": []
},
{
"name": "third folder",
"children": [
{
"name": "breads",
"children": []
},
{
"name": "coffee",
"children": [
{
"name": "latte",
"children": []
},
{
"name": "cappucino",
"children": []
},
{
"name": "mocha",
"children": []
},
]
},
]
}
]
Aiming to achieve the following output (similiar to file structure)
hello world
-fruits
-vegetables
-meats
--pork
--beef
--chicken
---organic
---farm raised
second folder
third folder
-breads
-coffee
--latte
--cappucino
--mocha
You could create recursive function using reduce method to iterate through your nested data structure, return array and then use join method on that array.
var sample = [{"name":"hello world","children":[{"name":"fruits","children":[]},{"name":"vegetables","children":[]},{"name":"meats","children":[{"name":"pork","children":[]},{"name":"beef","children":[]},{"name":"chicken","children":[{"name":"organic","children":[]},{"name":"farm raised","children":[]}]}]}]},{"name":"second folder","children":[]},{"name":"third folder","children":[{"name":"breads","children":[]},{"name":"coffee","children":[{"name":"latte","children":[]},{"name":"cappucino","children":[]},{"name":"mocha","children":[]}]}]}]
function tree(data, prev = '') {
return data.reduce((r, e) => {
r.push(prev + e.name)
if (e.children.length) r.push(...tree(e.children, prev + '-'));
return r;
}, [])
}
const result = tree(sample).join('\n')
console.log(result)
To create same structure in HTML you could use forEach method instead.
var sample = [{"name":"hello world","children":[{"name":"fruits","children":[]},{"name":"vegetables","children":[]},{"name":"meats","children":[{"name":"pork","children":[]},{"name":"beef","children":[]},{"name":"chicken","children":[{"name":"organic","children":[]},{"name":"farm raised","children":[]}]}]}]},{"name":"second folder","children":[]},{"name":"third folder","children":[{"name":"breads","children":[]},{"name":"coffee","children":[{"name":"latte","children":[]},{"name":"cappucino","children":[]},{"name":"mocha","children":[]}]}]}]
function tree(data, parent) {
const ul = document.createElement('ul');
data.forEach(el => {
const li = document.createElement('li');
li.textContent = el.name;
ul.appendChild(li);
if (el.children.length) {
tree(el.children, li)
}
})
parent.appendChild(ul)
}
const parent = document.getElementById('root')
tree(sample, parent)
<div id="root"></div>
var sample = [{"name":"hello world","children":[{"name":"fruits","children":[]},{"name":"vegetables","children":[]},{"name":"meats","children":[{"name":"pork","children":[]},{"name":"beef","children":[]},{"name":"chicken","children":[{"name":"organic","children":[]},{"name":"farm raised","children":[]}]}]}]},{"name":"second folder","children":[]},{"name":"third folder","children":[{"name":"breads","children":[]},{"name":"coffee","children":[{"name":"latte","children":[]},{"name":"cappucino","children":[]},{"name":"mocha","children":[]}]}]}]
level = 0;
var hyphens = '';
function recursive_loop(s) {
console.log(hyphens + s.name);
var c = s.children;
if (c.length) hyphens += '-';
var empty = false;
for (let i = 0; i < c.length; i++) {
if (c[i].children) {
recursive_loop(c[i]);
}
if (c[i].children.length)
empty = true;
}
if (empty) hyphens = '';
}
for (let i = 0; i < sample.length; i++) {
recursive_loop(sample[i]);
}
We use object-scan foe many data processing / traversal tasks. It's powerful once you wrap your head around it. Here is how you could solve your question
// const objectScan = require('object-scan');
const display = (input) => objectScan(['**'], {
reverse: false,
rtn: 'entry',
filterFn: ({ value }) => typeof value === 'string'
})(input)
.map(([k, v]) => `${'-'.repeat(k.length / 2 - 1)}${v}`);
const sample = [{ name: 'hello world', children: [{ name: 'fruits', children: [] }, { name: 'vegetables', children: [] }, { name: 'meats', children: [{ name: 'pork', children: [] }, { name: 'beef', children: [] }, { name: 'chicken', children: [{ name: 'organic', children: [] }, { name: 'farm raised', children: [] }] }] }] }, { name: 'second folder', children: [] }, { name: 'third folder', children: [{ name: 'breads', children: [] }, { name: 'coffee', children: [{ name: 'latte', children: [] }, { name: 'cappucino', children: [] }, { name: 'mocha', children: [] }] }] }];
const result = display(sample);
result.forEach((l) => console.log(l));
// => hello world
// => -fruits
// => -vegetables
// => -meats
// => --pork
// => --beef
// => --chicken
// => ---organic
// => ---farm raised
// => second folder
// => third folder
// => -breads
// => -coffee
// => --latte
// => --cappucino
// => --mocha
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

How to find a tree inside a tree in typescript

let say i have a tree in javascript
a1
--b
----c1
a2
--b2
--b3
----c2
and if i wanted to find c2, it should return a2->b3->c2
Lets say my json looked like this?
treeFamily = {
name : "Parent",
children: [{
name : "Child1",
children: [{
name : "Grandchild1",
children: []
},{
name : "Grandchild2",
children: []
},{
name : "Grandchild3",
children: []
}]
}, {
name: "Child2",
children: []
}]
};
You could check if the nested children have the wanted key/value. Then take the name and hand over the result to the outer call.
function findPath(array, target) {
var path;
array.some(({ name, children }) => {
var temp;
if (name === target) {
path = [name];
return true;
}
if (temp = findPath(children, target)) {
path = [name, ...temp];
return true;
}
});
return path;
}
var treeFamily = { name: "Parent", children: [{ name: "Child1", children: [{ name: "Grandchild1", children: [] }, { name: "Grandchild2", children: [] }, { name: "Grandchild3", children: [] }] }, { name: "Child2", children: [] }] };
console.log(findPath([treeFamily], 'Grandchild2'));
console.log(findPath([treeFamily], 'foo'));
You can use for...of to search the children by calling the function recursively. If the target is found, the name is returned, and combined with the previous names. If not, the function will return undefined. Alternatively, you can return an empty array.
const findPath = (targetName, { name, children }) => {
if(name === targetName) return [name];
for(const child of children) {
const result = findPath(targetName, child);
if(result) return [name, ...result];
}
// if child not found implicitly return undefined or return [] to get an empty array
};
const treeFamily = { name: "Parent", children: [{ name: "Child1", children: [{ name: "Grandchild1", children: [] }, { name: "Grandchild2", children: [] }, { name: "Grandchild3", children: [] }] }, { name: "Child2", children: [] }] };
console.log(findPath('Child2', treeFamily));
console.log(findPath('Grandchild3', treeFamily));
console.log(findPath('Grandchild400', treeFamily));

Categories