Recursive Map using Lodash - javascript

I have a data that needs to be recursive but I don't know how to implement it. Here is my data.
All I need to do is to look like this.
[
{
id: 1,
label: 'Satisfied customers',
children: [
{
id: 2,
label: 'Good food',
icon: 'restaurant_menu',
children: [
{ id: 3, label: 'Quality ingredients'},
{ id: 4, label: 'Good recipe' }
]
},
{
id: 5,
label: 'Good service',
icon: 'room_service',
children: [
{ id: 6, label: 'Prompt attention' },
{ id: 7, label: 'Professional waiter' }
]
},
{
id: 8,
label: 'Pleasant surroundings',
icon: 'photo',
children: [
{
id: 9,
label: 'Happy atmosphere (not tickable)',
tickable: false,
},
{
id: 10,
label: 'Good table presentation (disabled node)',
disabled: true,
},
{
id: 11,
label: 'Pleasing decor',
}
]
},
{
id: 12,
label: 'Extra information (has no tick)',
noTick: true,
icon: 'photo'
},
{
id: 13,
label: 'Forced tick strategy (to "strict" in this case)',
tickStrategy: 'strict',
icon: 'school',
children: [
{
id: 14,
label: 'Happy atmosphere',
},
{
id: 15,
label: 'Good table presentation',
},
{
id: 16,
label: 'Very pleasing decor',
}
]
}
]
}
]
My code doesn't works... it's just a map without any recursion.
categories.map(e => {
console.log(e.all_children)
return {
id: e.id,
label: e.name,
children: _.values(e).map(v => {
return { id: v.id, label: e.name }
})
}
})
I don't really know how to do it. If you have any idea on how to do it please help me. I've been searching how to do it using lodash but I can't find any relevant code. I'm not very good in javascript.

You need to specify a function for later mapping inside of the callback.
const
map = e => ({
id: e.id,
label: e.name,
children: e.all_children.map(map) // recursive call
}),
tree = categories.map(map);
To get all properties without all_children, you could take rest parameters ... for properties and separate just the children property for recursive mapping.
const
map = ({ all_children = [], ...o }) => ({
...o,
children: all_children.map(map) // recursive call
}),
tree = categories.map(map);

Related

Flat array to tree Javascript

Looking for a javascript or typescript solution to turn this array of sql data into a tree structure:
Some other solutions I've tried set the id as a property in the expected array but not the key of the object like in the expected solution.
const sqlData = [
{ id: 1, label: 'root', parentId: 0 },
{ id: 2, label: 'ant', parentId: 1 },
{ id: 3, label: 'cat', parentId: 1 },
{ id: 4, label: 'bear', parentId: 3 },
{ id: 5, label: 'dog', parentId: 3 },
{ id: 6, label: 'elephant', parentId: 5 },
{ id: 7, label: 'frog', parentId: 1 },
];
const expected = [
{
1: {
label: 'root',
children: [
{
2: {
label: 'ant',
children: [],
},
},
{
3: {
label: 'cat',
children: [
{
4: {
label: 'cat',
children: [],
},
},
{
5: {
label: 'dog',
children: [
{
6: {
label: 'elephant',
children: [],
},
},
],
},
},
],
},
},
{
7: {
label: 'frog',
children: [],
},
},
],
},
},
];
This can be done with O(n) time complexity by leveraging object references. By creating a children array on each element and then adding the correct child to its parent's children array, you can accomplish building the whole tree.
const sqlData=[{id:1,label:"root",parentId:0},{id:2,label:"ant",parentId:1},{id:3,label:"cat",parentId:1},{id:4,label:"bear",parentId:3},{id:5,label:"dog",parentId:3},{id:6,label:"elephant",parentId:5},{id:7,label:"frog",parentId:1}];
const parentMap = {};
const root = [];
// Map parent positions
sqlData.forEach((el, i) => {
parentMap[el.id] = i;
el.children = [];
});
sqlData.forEach(({ id, parentId, label, children }) => {
const insert = { [id]: { label, children } };
if (parentId === 0) {
root.push(insert);
return;
}
sqlData[parentMap[parentId]].children.push(insert);
});
console.log(root);

How to convert flat array containing a child "path" array into a nested array of objects based on the path?

I have an array of objects, each containing a path property which holds the value of "paths" to which I'd like to map the array elements to.
let myData = [
{
path: ['Movies', 'Comedies', 'TopRanked'],
name: 'The Hangover',
id: '1',
},
{
path: ['Movies', 'Comedies', 'TopRanked'],
name: 'Eurotrip',
id: '2',
},
{
path: ['Movies', 'Action'],
name: 'Need for Speed',
id: '3',
},
{
path: ['Life'],
name: 'Not so bad',
id: '4',
},
{
path: ['Life', 'Financial', 'Income'],
name: 'Making Hundreds',
id: '5',
},
{
path: ['Life', 'Financial', 'Income'],
name: 'Making Thousands',
id: '6',
},
{
path: ['Life', 'MonthlySpent'],
name: 'Just a little bit',
id: '7',
},
{
path: ['Life', 'MonthlySpent'],
name: 'Living large',
id: '8',
},
];
console.log(myData);
Essentially, the result I am looking for is a breakdown of that array into as many as nested arrays as needed (relative to all possible available paths), with each retaining its "type" - either a parent or an item. So the desired output is like so:
let myTree = [
{
name: 'Movies',
type: 'parent',
children: [
{
name: 'Comedies',
type: 'parent',
children: [
{
name: 'TopRanked',
type: 'parent',
children: [
{
name: 'The Hangover',
type: 'item',
id: 1,
path: ['Movies', 'Comedies', 'TopRanked']
},
{
name: 'Eurotrip',
type: 'item',
id: 2,
path: ['Movies', 'Comedies', 'TopRanked'],
}
]
},
]
},
{
name: 'Action',
type: 'parent',
children: [
{
name: 'Need for Speed',
type: 'item',
id: 3,
path: ['Movies', 'Action'],
},
]
}
]
},
{
name: 'Life',
type: 'parent',
children: [
{
name: 'Not so bad',
type: 'item',
id: 4,
path: ['Life'],
},
{
name: 'Financial',
type: 'parent',
children: [
{
name: 'Income',
type: 'parent',
children: [
{
name: 'Making Hundreds',
type: 'item',
id: 5,
path: ['Life', 'Financial', 'Income'],
},
{
name: 'Making Thousands',
type: 'item',
id: 6,
path: ['Life', 'Financial', 'Income'],
}
]
}
]
},
{
name: 'MonthlySpent',
type: 'parent',
children: [
{
name: 'Just a little bit',
type: 'item',
id: 7,
path: ['Life', 'MonthlySpent'],
},
{
name: 'Living Large',
type: 'item',
id: 8,
path: ['Life', 'MonthlySpent'],
}
]
}
]
}
]
console.log(myTree);
I tried the following, and while the tree structure is created, the "item"-types are not placed as the array-value of the last nested "parent" type:
function treeData(data) {
var result = [],
hash = { _: { children: result } };
data.forEach(function (object) {
object.path.reduce(function (o, p) {
if (!o[p]) {
o[p] = { _: { name: p, children: [] } };
o._.children.push(o[p]._);
}
return o[p];
}, hash)._.name = object.name;
});
return result;
}
Would appreciate a working solution, as I am wracking my head and can't find one. Tnnx.
The approach below follows a similar pattern to your code i.e. loop every object, but instead of a reduce simply loops every item in path and creates a branch off the root. When there are no more 'branches' then add the original object. See the comments.
let myData = data();
let myTree = treeData(data);
console.log(myTree);
function treeData(data) {
let root = {"children": []} // create origin
for (obj of myData) { // loop items in the data
obj.type = "Item"; // add a property to suit your output
let tree = root; // start at root every object
for (path of obj.path) { // loop over items in path
let branch = tree.children.find(k => k.name == path); // look for branch
if (!branch) { // if no branch, create one
branch = {"name": path, "type": "parent", "children": []}
tree.children.push(branch); // push this into children of current level
}
tree = branch; // set tree to branch before processing next item in path
}
tree.children.push(obj); // add the item to the hierarchy after path is exhausted
}
return root.children; // return children of the root to suit your output
}
function data() {
return [
{
path: ['Movies', 'Comedies', 'TopRanked'],
name: 'The Hangover',
id: '1',
},
{
path: ['Movies', 'Comedies', 'TopRanked'],
name: 'Eurotrip',
id: '2',
},
{
path: ['Movies', 'Action'],
name: 'Need for Speed',
id: '3',
},
{
path: ['Life'],
name: 'Not so bad',
id: '4',
},
{
path: ['Life', 'Financial', 'Income'],
name: 'Making Hundreds',
id: '5',
},
{
path: ['Life', 'Financial', 'Income'],
name: 'Making Thousands',
id: '6',
},
{
path: ['Life', 'MonthlySpent'],
name: 'Just a little bit',
id: '7',
},
{
path: ['Life', 'MonthlySpent'],
name: 'Living large',
id: '8',
},
];
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

Javascript/Typescript: How can I set one known property in a deep nested javascript array

I have the next object:
private obj: someclass[] =
[
{
id: 1,
label: 'label 1',
type: { name: 'checkbox' },
options: [
{ id: 1, isChecked: false},
{ id: 2, isChecked: false}
]
},
{
id: 2,
label: 'label 2',
type: { name: 'checkbox' },
options: [
{ id: 3, isChecked: false},
{ id: 4, isChecked: false}
]
}
]
All I want to do is set every property named "isChecked" to false
I can probably create a new empty array and with a foreach loop and indexes recreate the whole object but with that property set to false, but Im im guessing that this being a common operation it should be simplier right? I tried mapping but all I got was undefined, can someone please point me to the right direction? Thanks!
Something like this for example (every isChecked property is set to true):
let arr = [
{
id: 1,
label: 'label 1',
type: { name: 'checkbox' },
options: [
{ id: 1, isChecked: false},
{ id: 2, isChecked: false}
]
},
{
id: 2,
label: 'label 2',
type: { name: 'checkbox' },
options: [
{ id: 3, isChecked: false},
{ id: 4, isChecked: false}
]
}
];
arr.forEach((item) => {
item.options.every(x => x.isChecked = true);
});
console.log(arr);

How to change the value of BehaviorSubject using map/filter/find/findIndex in angular

TS
arrData = new BehaviorSubject<any>([]);
ngOnInit() {
const dataArr1 = [
{
id: '1',
name: 'Room1',
spinning: true
},
{
id: '2',
name: 'Room2',
spinning: true
},
{
id: '3',
name: 'Room3',
spinning: true
},
{
id: '4',
name: 'Room4',
spinning: true
}
];
this.arrData.next(dataArr1);
const url = { id: '2', name: 'Room2', link: '/api/conditions'}
const arr = new Array();
arr.push(url);
this.arrData.value.map((item: any) => {
return {
id: item.id,
name: item.name,
spinning: arr.findIndex(e => {
return e.id === item.id
}) === -1
}
});
console.log(this.arrData.value);
}
here's the sample output:
https://stackblitz.com/edit/angular-msovvq?file=src/app/app.component.ts
What I'm trying to do here is to change the value of spinning to false.
in the example there's data object { id: 2, name: 'Room2' } then I push it to make it an array.
where I used the findIndex where dataArr1 id === arr id, but the problem here it doesn't change the spinning value to false of dataArr1.
it still the same.
[
{
id: '1',
name: 'Room1',
spinning: true
},
{
id: '2',
name: 'Room2',
spinning: true
},
{
id: '3',
name: 'Room3',
spinning: true
},
{
id: '4',
name: 'Room4',
spinning: true
}
];
it should be like this.
[
{
id: '1',
name: 'Room1',
spinning: true
},
{
id: '2',
name: 'Room2',
spinning: false
},
{
id: '3',
name: 'Room3',
spinning: true
},
{
id: '4',
name: 'Room4',
spinning: true
}
];
the Room2 spinning will change to false.
I suggest you using map along with filter like this. Map and filter return a new array, not change value itself, so rebind the result to the variable.
dataArr1 = dataArr1.map(item => {
return {
id: item.id,
name: item.name,
spinning: dataArr2.findIndex(e => {
return e.id === item.id
}) === -1
}
})
This is the result, is this what you expected?
[
{
"id": "1",
"name": "Room1",
"spinning": false
},
{
"id": "2",
"name": "Room2",
"spinning": false
},
{
"id": "3",
"name": "Room3",
"spinning": false
},
{
"id": "4",
"name": "Room4",
"spinning": true
}
]
Using lodash
let tmp = [
{
id: '1',
name: 'Room1',
spinning: true
},
{
id: '2',
name: 'Room2',
spinning: true
},
{
id: '3',
name: 'Room3',
spinning: true
},
{
id: '4',
name: 'Room4',
spinning: true
}
];
Since we are only using id to find:
let url = { id: '2', name: 'Room2', link: '/api/conditions'}
let _tmp_row= _.find(tmp,{id:url['id']})
_tmp_row.spinning = false
Once you find the element as per your pattern you can change the object property as per you like. Since this object is not cloned it will directly update the changes to the main array of objects.

Cannot Push Data Children of Children in Nested Array

So I have the following Array. I can push to the parent level and child level however I can't seem to push to Children of Children or deeper. Here's the Array:
TREE_DATA: SegmentCategory[] = [
{
id: 1,
name: 'Category 1',
children: [
{
id: 2,
name: 'Category 1-1',
children: [
{
id: 4,
name: 'Category 1-1-a',
children: []
},
{
id: 5,
name: 'Category 1-1-b',
children: []
},
]
},
{
id: 6,
name: 'Category 1-2',
children: [
{
id: 7,
name: 'Category 1-2-a',
children: [
{
id: 8,
name: 'Category 1-2-1',
children: [
{
id: 9,
name: 'Category 1-2-2-a',
children: []
}
]
}
]
}
]
}
]
},
{
id: 10,
name: 'Category 2',
children: []
}
];
And here is the method I am using to add data to the array:
createCategory() {
this.id++;
if (this.formGroup.controls['parentCategory'].value == null) {
this.TREE_DATA.push(
{
id: this.id,
name: this.formGroup.controls['categoryName'].value,
children: []
});
} else {
this.TREE_DATA.forEach((v, index) => {
if (v.id === this.formGroup.controls['parentCategory'].value.id) {
this.TREE_DATA[index].children.push(
{
id: this.id,
name: this.formGroup.controls['categoryName'].value,
children: []
}
);
}
});
}
}
I know that I can just add an additional foreach however this doesn't seem like the best way to handle this, as I could want to go 5-6 layers down at any time to add a new nested child. What would be the best way to handle this?
You could take a recursive function for finding an object in the tree.
function find(array, id) {
var result;
array.some(object => {
if (object.id === id) return result = object;
return result = find(object.children || [], id);
});
return result;
}
var data = [{ id: 1, name: 'Category 1', children: [{ id: 2, name: 'Category 1-1', children: [{ id: 4, name: 'Category 1-1-a', children: [] }, { id: 5, name: 'Category 1-1-b', children: [] }] }, { id: 6, name: 'Category 1-2', children: [{ id: 7, name: 'Category 1-2-a', children: [{ id: 8, name: 'Category 1-2-1', children: [{ id: 9, name: 'Category 1-2-2-a', children: [] }] }] }] }] }, { id: 10, name: 'Category 2', children: [] }];
console.log(find(data, 9));
console.log(find(data, 'unknown'));

Categories

Resources