flatten tree with javascript and es6 features - javascript

i am new in es6 and want to flatten my tree object.
(I'm using reflux - not redux - but flatten state is also a good idea in reflux)
api response:
export const node = {
item: 1,
children: [
{
item: 2,
children: [
{
item: 3,
children: [
{
item: 4,
children: []
},
{
item: 5,
children: []
},
{
item: 6,
children: [
{
item: 7,
children: []
},
{
item: 8,
children: []
},
{
item: 9,
children: []
}
]
}
]
},
{
item: 10,
children: [
{
item: 11,
children: []
},
{
item: 12,
children: [
{
item: 13,
children: []
},
{
item: 14,
children: []
}
]
}
]
}
]
}
]
}
My goal is:
tree= {
byId: {
item1 : {
id: 'item1',
name: 'item1', parent: null,
children : ['item2']
}
}
parent is one id, childrend are array of ids
for building a breadcrumb (using parent) or listing child objects...
get object from id with
tree.byId[someId]
my last try ist to use a recursiv function with es6 spread operator:
const flattenTree = (tree, flattenTree) => {
if (node.children.length) {
node.children.map(child => {
return flattenTree(child, [...tree= { id: child.item}])
})
} else {
return [...tree, tree.byId[cat.item] = { id: cat.item, name: cat.item }]
}
}
sorry I'm first time here, so my post in not well formatted...
thx for help

const tree = {
item: 1,
children: [
{
item: 2,
children: [
{
item: 3,
children: [
{
item: 4,
children: []
},
{
item: 5,
children: []
},
{
item: 6,
children: [
{
item: 7,
children: []
},
{
item: 8,
children: []
},
{
item: 9,
children: []
}
]
}
]
},
{
item: 10,
children: [
{
item: 11,
children: []
},
{
item: 12,
children: [
{
item: 13,
children: []
},
{
item: 14,
children: []
}
]
}
]
}
]
}
]
}
const flattenTree = (tree) => {
const newTree = { byId: {} };
const traverseNode = (node, parent) => {
const id = `item${node.item}`;
newTree.byId[id] = {
id,
name: id,
parent,
children: node.children.map((c) => {
traverseNode(c, id)
return `item${c.item.id}`;
})
}
}
traverseNode(tree, null);
return newTree;
}
The result of flattenTree(tree) will look something like this:
{ byId:
{ item4:
{ id: 'item4', name: 'item4', parent: 'item3', children: [] },
item5:
{ id: 'item5', name: 'item5', parent: 'item3', children: [] },
item7:
{ id: 'item7', name: 'item7', parent: 'item6', children: [] },
item8:
{ id: 'item8', name: 'item8', parent: 'item6', children: [] },
item9:
{ id: 'item9', name: 'item9', parent: 'item6', children: [] },
item6:
{ id: 'item6', name: 'item6', parent: 'item3', children: [Array] },
item3:
{ id: 'item3', name: 'item3', parent: 'item2', children: [Array] },
item11:
{ id: 'item11', name: 'item11', parent: 'item10', children: [] },
item13:
{ id: 'item13', name: 'item13', parent: 'item12', children: [] },
item14:
{ id: 'item14', name: 'item14', parent: 'item12', children: [] },
item12:
{ id: 'item12',
name: 'item12',
parent: 'item10',
children: [Array] },
item10:
{ id: 'item10',
name: 'item10',
parent: 'item2',
children: [Array] },
item2:
{ id: 'item2', name: 'item2', parent: 'item1', children: [Array] },
item1:
{ id: 'item1', name: 'item1', parent: null, children: [Array] } } }

You can use this recursive function:
Main() {
let flattedTree = [];
// We must treat the tree like a node, so we put it inside an array.
let tree = [
{
item: 1,
children: [
{
item: 2,
children: [
{
item: 3,
children: [
{
item: 4,
children: [],
},
{
item: 5,
children: [],
},
{
item: 6,
children: [
{
item: 7,
children: [],
},
{
item: 8,
children: [],
},
{
item: 9,
children: [],
},
],
},
],
},
{
item: 10,
children: [
{
item: 11,
children: [],
},
{
item: 12,
children: [
{
item: 13,
children: [],
},
{
item: 14,
children: [],
},
],
},
],
},
],
},
],
},
];
// After recursive method executed you will have a flattend array.
// flattedTree variable hold the flatted tree.
this.flatten(tree, flattedTree);
}
flatten(nodes: any[], flattedNodes: any[]) {
for (let index = 0; index < nodes.length; index++) {
flattedNodes.push(nodes[index]);
if (nodes[index].children !== undefined)
if (nodes[index].children.length > 0)
this.flatten(nodes[index].children, flattedNodes);
}
}
This is the result:
[{"item":1,"children":[{"item":2,"children":[{"item":3,"children":[{"item":4,"children":[]},{"item":5,"children":[]},{"item":6,"children":[{"item":7,"children":[]},{"item":8,"children":[]},{"item":9,"children":[]}]}]},{"item":10,"children":[{"item":11,"children":[]},{"item":12,"children":[{"item":13,"children":[]},{"item":14,"children":[]}]}]}]}]},{"item":2,"children":[{"item":3,"children":[{"item":4,"children":[]},{"item":5,"children":[]},{"item":6,"children":[{"item":7,"children":[]},{"item":8,"children":[]},{"item":9,"children":[]}]}]},{"item":10,"children":[{"item":11,"children":[]},{"item":12,"children":[{"item":13,"children":[]},{"item":14,"children":[]}]}]}]},{"item":3,"children":[{"item":4,"children":[]},{"item":5,"children":[]},{"item":6,"children":[{"item":7,"children":[]},{"item":8,"children":[]},{"item":9,"children":[]}]}]},{"item":4,"children":[]},{"item":5,"children":[]},{"item":6,"children":[{"item":7,"children":[]},{"item":8,"children":[]},{"item":9,"children":[]}]},{"item":7,"children":[]},{"item":8,"children":[]},{"item":9,"children":[]},{"item":10,"children":[{"item":11,"children":[]},{"item":12,"children":[{"item":13,"children":[]},{"item":14,"children":[]}]}]},{"item":11,"children":[]},{"item":12,"children":[{"item":13,"children":[]},{"item":14,"children":[]}]},{"item":13,"children":[]},{"item":14,"children":[]}]

Related

How to split recursive array into two categories based on specific field and also get the parent path?

I have confusion on recursive function to split recursive array into another recursive arrays. Let say I have an item and inside it there is packaged items (or children), and the children also have an item and children, there's no limitation about the recursion level.
The problem
I need to separate the paths of package item object that has isOptional true and paths of isOptional false. The function should return 2 categorized array and the value inside the array must be recursive just like the input structure. Please check the diagram below
Here's input example
const product = {
name: "Product 1",
packagedItems: [
{
id: 1,
isOptional: false,
item: {
name: "1#1",
packagedItems: [
{
id: 3,
isOptional: false,
item: {
name: "2#1",
packagedItems: [
{
id: 5,
isOptional: false,
item: {
name: "3#1",
packagedItems: []
}
},
{
id: 6,
isOptional: true,
item: {
name: "3#2",
packagedItems: []
}
}
]
}
}
]
}
},
{
id: 2,
isOptional: false,
item: {
name: "1#2",
packagedItems: [
{
id: 4,
isOptional: false,
item: {
name: "2#2",
packagedItems: [
{
id: 7,
isOptional: true,
item: {
name: "3#3",
packagedItems: []
}
},
{
id: 8,
isOptional: true,
item: {
name: "3#4",
packagedItems: []
}
}
]
}
}
]
}
}
]
};
Here's the diagram
enter image description here
What I've tried is only able to get parent's name but not building the same structure as the input
const getParents = (
packagedItems: PackagedItem[],
ancestors: (string | PackagedItem)[] = []
): any => {
for (let pack of packagedItems) {
if (pack.isOptional && !pack.item.packagedItems.length) {
return ancestors.concat(pack);
}
const found = getParents(
pack.item.packagedItems,
ancestors.concat(pack.item.name)
);
if (found) {
return found;
}
}
return undefined;
};
console.log(getParents(product.packagedItems));
only return
[
"1#1",
"2#1",
{
id: 6
isOptional: true
item: Object
}
]
Expected result would be two arrays like this.
const optionalTrue = [
{
id: 1,
isOptional: false,
item: {
name: "1#1",
packagedItems: [
{
id: 3,
isOptional: false,
item: {
name: "2#1",
packagedItems: [
{
id: 6,
isOptional: true,
item: {
name: "3#2",
packagedItems: []
}
}
]
}
}
]
}
},
{
id: 2,
isOptional: false,
item: {
name: "1#2",
packagedItems: [
{
id: 4,
isOptional: false,
item: {
name: "2#2",
packagedItems: [
{
id: 7,
isOptional: true,
item: {
name: "3#3",
packagedItems: []
}
},
{
id: 8,
isOptional: true,
item: {
name: "3#4",
packagedItems: []
}
}
]
}
}
]
}
}
];
const optionalFalse = [
{
id: 1,
isOptional: false,
item: {
name: "1#1",
packagedItems: [
{
id: 3,
isOptional: false,
item: {
name: "2#1",
packagedItems: [
{
id: 5,
isOptional: false,
item: {
name: "3#1",
packagedItems: []
}
}
]
}
}
]
}
}
];
Your function is going in the right direction, but the pack is a leaf, and matches the category, you should not continue with the recursive call. Also the collection into the ancestors array will not work well as you collect either pack or name in it.
Here is an implementation that gives the output you intended:
function filterOnLeaf(item, optional) {
return {
...item,
packagedItems: item.packagedItems.map(package => {
if (!package.item.packagedItems.length) { // Leaf
if (package.isOptional != optional) return null;
} else { // Internal
package = {
...package,
item: filterOnLeaf(package.item, optional)
};
if (!package.item.packagedItems.length) return null;
}
return package;
}).filter(Boolean)
};
}
const product = {name: "Product 1",packagedItems: [{id: 1,isOptional: false,item: {name: "1#1",packagedItems: [{id: 3,isOptional: false,item: {name: "2#1",packagedItems: [{id: 5,isOptional: false,item: {name: "3#1",packagedItems: []}},{id: 6,isOptional: true,item: {name: "3#2",packagedItems: []}}]}}]}},{id: 2,isOptional: false,item: {name: "1#2",packagedItems: [{id: 4,isOptional: false,item: {name: "2#2",packagedItems: [{id: 7,isOptional: true,item: {name: "3#3",packagedItems: []}},{id: 8,isOptional: true,item: {name: "3#4",packagedItems: []}}]}}]}}]};
const optionalTrue = filterOnLeaf(product, true);
const optionalFalse = filterOnLeaf(product, false);
console.log("optional is true:");
console.log(optionalTrue);
console.log("optional is false:");
console.log(optionalFalse);

In a javascript nested object, How to get all parents attribute chain by a property like id?(If possible, vanilla javascript)

My object list looks like this:
const a = [
{
id: 1,
name: 'NewYork',
children: [],
},
{
id: 2,
name: 'Tokyo',
children: [
{
id: 7,
name: 'Toshima',
children: [],
},
{
id: 8,
name: 'Minato',
children: [
{
id: 17,
name: 'Sugamo',
children: [],
},
{
id: 18,
name: 'Kamiichi',
children: [],
},
],
},
],
},
];
And now I have an id 18, I'd like to make a String like Tokyo>Minato>Kamiichi
How can I make it work in javascript (no third party library) ?
Actually I have a very long nested object.
One way is to use a recursive function for this:
const a = {
companies: [{
id: 1,
name: 'NewYork',
children: [],
},
{
id: 2,
name: 'Tokyo',
children: [{
id: 7,
name: 'Toshima',
children: [],
},
{
id: 8,
name: 'Minato',
children: [{
id: 17,
name: 'Sugamo',
children: [],
},
{
id: 18,
name: 'Kamiichi',
children: [],
},
],
},
],
},
],
};
function getPath(obj, id) {
if (obj.id === id) return obj.name;
for (const child of obj.children) {
const path = getPath(child, id);
if (path) {
return obj.name ? obj.name + '>' + path : path;
}
}
return null;
}
console.log(getPath({
children: a.companies
}, 18));

Converting JS object to different order

I have JS object like this one:
const networkTree = [
{
id: 10,
hasChildren: true,
children: [
{
id: 9,
hasChildren: true,
children: [
{
id: 7,
hasChildren: true,
children: [
{
id: 5,
hasChildren: false,
children: [],
},
{
id: 4,
hasChildren: false,
children: [],
},
],
},
{
id: 6,
hasChildren: true,
children: [
{
id: 3,
hasChildren: false,
children: [],
},
{
id: 2,
hasChildren: false,
children: [],
},
],
},
],
},
{
id: 8,
hasChildren: true,
children: [
{
id: 1,
hasChildren: false,
children: [],
},
{
id: 11,
hasChildren: false,
children: [],
},
],
},
],
},
];
I have to convert it with a function takes input js object like above to this order:
const myTreeData = [
{
name: 'Top Level',
children: [
{
name: 'Level 2: A',
},
{
name: 'Level 2: B',
},
],
},
];
I am stucked at this point:
const convertTree = (array) => (
treeDatas[];
treeData[];
for(i = 0 ; i < array.length ; i++) {
treeDatas.push(array[i]);
treeData[i].name.push(array[i].id);
}
);
If input networkTree[0].id is 15, myTreeData[0].name should be 15. I want to use id's as names on converted object. I don't need hasChildren boolean value. I will use converted object format with react-ds3-component. If you need more information about converted object format please check https://www.npmjs.com/package/react-d3-tree
If I understand you correctly you want to rename id to name and remove hasChildren? Then you can use this recursive function.
const convertTree = (tree) =>
tree.map((node) => ({
name: node.id,
children: convertTree(node.children)
}))
It's the same as this function, just less verbal.
const convertTree = (tree) => {
return tree.map((node) => {
return {
name: node.id,
children: convertTree(node.children)
}
})
}

Remove items from nested array of objects based on a condition

In my application, I have data returned from the server like below. It has very deep nesting:
var data = [{
name: "root",
children: [{
name: "Parent1",
children: [{
name: "Parent1-child1",
children: [{
name: "Parent1-child1-grandchild1",
children: [{
name: "Parent1-child1-grandchild1-last",
children:[]
}]
},
{
name: "Parent1-child1-grandchild2",
children: []
},
{
name: "Parent1-child1-grandchild3",
children: []
}
]
},
{
name: "Paren1-child2",
children: [{
name: "Parent1-chil2-grandchild1",
children: []
},
{
name: "Parent1-child2-grandchild2",
children: [{
name: "Parent1-child2-grandchild2-last",
children: []
}]
},
{
name: "Parent1-child2-grandchild3",
children: []
}
]
},
{
name: "Parent1-child3",
children: []
}
]
},
{
name: "Parent2",
children: [{
name: "Parent2-child1",
children: []
},
{
name: "Parent2-child2",
children: [{
name: "Parent2-child2-grandchild1",
children: []
},
{
name: "Parent2-child2-grandchild2",
children: [{
name: "Parent2-child2-grandchild2-last",
children: []
}]
}
]
}
]
},
{
name: "Parent3",
children: []
}
]
}];
The requirement is to loop through all the objects (deep nested level also) and remove the object if the children property has a value of an empty array. So the output should be like below
var data = [{
name: "root",
children: [{
name: "Parent1",
children: [{
name: "Parent1-child1",
children: [{
name: "Parent1-child1-grandchild1",
children: []
},
]
},
{
name: "Paren1-child2",
children: [
{
name: "Parent1-child2-grandchild2",
children: []
},
]
},
]
},
{
name: "Parent2",
children: [
{
name: "Parent2-child2",
children: [
{
name: "Parent2-child2-grandchild2",
children: []
}
]
}
]
}
]
}];
I have tried the following code, but it doesn't work as expected. Please let me know how to achieve the expected result.
function checkChildrens(arr) {
arr.forEach((ele,i) => {
if(ele.hasOwnProperty('children')) {
checkChildrens(ele['children'])
} else {
arr.splice(i,1)
}
})
}
checkChildrens(data);
I have tried with the filter method also in that case. It is not working correctly.
arr.filter((ele,i)=>{
if(ele.hasOwnProperty('children') && ele.children.length !== 0 ){
removeEmpty(ele.children)
}else{
return false;
}
return true;
})
You could rebuild new objects by checking the children array length.
function filter(array) {
return array.reduce((r, o) => {
if (o.children && o.children.length) {
r.push(Object.assign({}, o, { children: filter(o.children) }));
}
return r;
}, []);
}
var data = [{ name: "root", children: [{ name: "Parent1", children: [{ name: "Parent1-child1", children: [{ name: "Parent1-child1-grandchild1", children: [{ name: "Parent1-child1-grandchild1-last", children: [] }] }, { name: "Parent1-child1-grandchild2", children: [] }, { name: "Parent1-child1-grandchild3", children: [] }] }, { name: "Paren1-child2", children: [{ name: "Parent1-chil2-grandchild1", children: [] }, { name: "Parent1-child2-grandchild2", children: [{ name: "Parent1-child2-grandchild2-last", children: [] }] }, { name: "Parent1-child2-grandchild3", children: [] }] }, { name: "Parent1-child3", children: [] }] }, { name: "Parent2", children: [{ name: "Parent2-child1", children: [] }, { name: "Parent2-child2", children: [{ name: "Parent2-child2-grandchild1", children: [] }, { name: "Parent2-child2-grandchild2", children: [{ name: "Parent2-child2-grandchild2-last", children: [] }] }] }] }, { name: "Parent3", children: [] }] }],
result = filter(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Approach for removing all nested empty children (except the last one. This has an empty object, but no children property).
function filter(array) {
return array.reduce((r, o) => {
if (o.children) {
var children = filter(o.children);
if (children.length) r.push(Object.assign({}, o, { children }));
} else {
r.push(o);
}
return r;
}, []);
}
var data = [{ name: "root", children: [{ name: "Parent1", children: [{ name: "Parent1-child1", children: [{ name: "Parent1-child1-grandchild1", children: [{ name: "Parent1-child1-grandchild1-last", children: [] }] }, { name: "Parent1-child1-grandchild2", children: [] }, { name: "Parent1-child1-grandchild3", children: [] }] }, { name: "Paren1-child2", children: [{ name: "Parent1-chil2-grandchild1", children: [] }, { name: "Parent1-child2-grandchild2", children: [{ name: "Parent1-child2-grandchild2-last", children: [] }] }, { name: "Parent1-child2-grandchild3", children: [] }] }, { name: "Parent1-child3", children: [] }] }, { name: "Parent2", children: [{ name: "Parent2-child1", children: [] }, { name: "Parent2-child2", children: [{ name: "Parent2-child2-grandchild1", children: [] }, { name: "Parent2-child2-grandchild2", children: [{ name: "Parent2-child2-grandchild2-last", children: [] }] }] }] }, { name: "Parent3", children: [{}] }] }],
result = filter(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var data = [{
name: "root",
children: [{
name: "Parent1",
children: [{
name: "Parent1-child1",
children: [{
name: "Parent1-child1-grandchild1",
children: [{
name: "Parent1-child1-grandchild1-last",
children: []
}]
},
{
name: "Parent1-child1-grandchild2",
children: []
},
{
name: "Parent1-child1-grandchild3",
children: []
}
]
},
{
name: "Paren1-child2",
children: [{
name: "Parent1-chil2-grandchild1",
children: []
},
{
name: "Parent1-child2-grandchild2",
children: [{
name: "Parent1-child2-grandchild2-last",
children: []
}]
},
{
name: "Parent1-child2-grandchild3",
children: []
}
]
},
{
name: "Parent1-child3",
children: []
}
]
},
{
name: "Parent2",
children: [{
name: "Parent2-child1",
children: []
},
{
name: "Parent2-child2",
children: [{
name: "Parent2-child2-grandchild1",
children: []
},
{
name: "Parent2-child2-grandchild2",
children: [{
name: "Parent2-child2-grandchild2-last",
children: []
}]
}
]
}
]
},
{
name: "Parent3",
children: []
}
]
}];
function checkChildrens(arr) {
let res = []
arr.forEach(v => {
if (v.children && v.children.length) {
res = res.concat({
name: v.name,
children: checkChildrens(v.children)
})
}
})
return res
}
console.log(checkChildrens(data));

how to filter nested array like this?

I am having response like below
let m = [
{
name: 'Summary',
subListExpanded: false,
subList: [
]
},
{
name: 'Upload',
subListExpanded: false,
subList: [
]
},
{
name: 'Tasks',
subListExpanded: false,
subList: [
]
},
{
name: 'Dashboard',
subListExpanded: false,
subList: [
]
},
{
name: 'Master',
subListExpanded: false,
subList: [
{
id: 'user-master',
name: 'User-Master'
},
{
id: 'menu-master',
name: 'Menu-Master'
},
{
id: 'entity-master',
name: 'Entity-Master'
},
{
id: 'vendor-master',
name: 'Vendor-Master'
},
{
id: 'xxx-master',
name: 'xxx-Master'
}
]
}
];
If i search m the filter should be like this
[
{
name: 'Summary',
subListExpanded: false,
subList: [
]
},
{
name: 'Master',
subListExpanded: false,
subList: [
{
id: 'user-master',
name: 'User-Master'
},
{
id: 'menu-master',
name: 'Menu-Master'
},
{
id: 'entity-master',
name: 'Entity-Master'
},
{
id: 'vendor-master',
name: 'Vendor-Master'
},
{
id: 'xxx-master',
name: 'xxx-Master'
}
]
}
];
if i search master the filter response should like this?
[
{
name: 'Master',
subListExpanded: false,
subList: [
{
id: 'user-master',
name: 'User-Master'
},
{
id: 'menu-master',
name: 'Menu-Master'
},
{
id: 'entity-master',
name: 'Entity-Master'
},
{
id: 'vendor-master',
name: 'Vendor-Master'
},
{
id: 'xxx-master',
name: 'xxx-Master'
}
]
}
];
if i search xxx-master the filter response should be
[
{
name: 'Master',
subListExpanded: false,
subList: [
{
id: 'xxx-master',
name: 'xxx-Master'
}
]
}
];
if i search slkvcsmcskc filter response like
[]
my typescript code is not working properly .please help me to fix this>
m.filter(x=> x.name.toLowerCase() === search.toLowerCase() || x.subList.some(x1=> x1.name.toLowerCase()===search.toLowerCase()))
The following code gives the desired output. Note that I added some complexity which may not be needed for your use case. However, the example should work for lists with arbitrary deep nesting (see 'bar' example).
let m = [
{
name: 'Summary',
subListExpanded: false,
subList: [
]
},
{
name: 'Upload',
subListExpanded: false,
subList: [
{
name: 'foo',
subList: [
{
name: 'bar',
}
],
}
]
},
{
name: 'Tasks',
subListExpanded: false,
subList: [
]
},
{
name: 'Dashboard',
subListExpanded: false,
subList: [
]
},
{
name: 'Master',
subListExpanded: false,
subList: [
{
id: 'user-master',
name: 'User-Master'
},
{
id: 'menu-master',
name: 'Menu-Master'
},
{
id: 'entity-master',
name: 'Entity-Master'
},
{
id: 'vendor-master',
name: 'Vendor-Master'
},
{
id: 'xxx-master',
name: 'xxx-Master'
}
]
}
];
function search (input, query) {
const queryReg = new RegExp(query, 'i');
function searchInternal (data) {
let result = [];
data.forEach(item => {
const parentMatch = queryReg.test(item.name);
let subMatch = false;
if (item.subList) {
let subResult = searchInternal(item.subList);
subMatch = subResult.length > 0;
item.subList = subMatch ? subResult : [];
}
// push parent if it matches for itself or a child (list) matches
if (parentMatch || subMatch) result.push(item);
});
return result;
}
return searchInternal(JSON.parse(JSON.stringify(input)) /* create a working copy with JSON.parse(...) */);
}
console.log('master', search(m, 'master'));
console.log('xxx-master', search(m, 'xxx-master'));
console.log('m', search(m, 'm'));
console.log('bar', search(m, 'bar'));
console.log('slkvcsmcskc', search(m, 'slkvcsmcskc'));
Actually it should go like this:
obj = {
_id: "sjkd9skj",
data: {
dektop: [
{
x: 2,
y: 3,
t: { key: 'aabbcc'}
},
...
],
mobile: [
{
x: 4,
y: 3,
t: { key: 'ffff'}
},
...
],
print: [
{
x: 7,
y: 5,
t: { key: 'ppp'}
},
...
]
}
}

Categories