Setting property value of object in a deep javascript Array - javascript

I was wondering if anyone could help figure out how i can set the value of a property in an object which can be found in a deep Array.
Below is an example of the Tree array
I would like to know how i can insert
var newObjectToInsert = {id: 999, name: 'new name'};
in the 'nodes' array of the object whose id === 3901
var tree = [
{
id: 1,
name: 'Level 1 - A',
nodes: [
{
id: 33,
name: 'Level 2 = A',
nodes: []
},
{
id: 21,
name: 'Level 2 = B',
nodes: []
}
]
},
{
id: 2,
name: 'Level 1 - B',
nodes: []
},
{
id: 3,
name: 'Level 1 - B',
nodes: [
{
id: 65,
name: 'Level 2 = A',
nodes: []
},
{
id: 124,
name: 'Level 2 = A',
nodes: [
{
id: 3901,
name: 'Level 3 - A'
},
{
id: 29182,
name: 'Level 3 - B',
nodes: [
{
id: 32423413,
name: 'Level 4 - A'
}
]
}
]
},
{
id: 534,
name: 'Level 2 = A',
nodes: []
}
]
},
];

You can use a native Array#some to achieve a recursive traversal. An advantage towards using this approach is it already provides a mechanism to stop the traversal once it finds the node that we want to insert the new object.
var inserted = tree.some(function cb(v) {
var nodes = v.nodes || [];
return v.id === nodeId?
(v.nodes = nodes).push(newObjectToInsert):
nodes.some(cb);
});
var tree = [{
id: 1,
name: 'Level 1 - A',
nodes: [{
id: 33,
name: 'Level 2 = A',
nodes: []
},
{
id: 21,
name: 'Level 2 = B',
nodes: []
}
]
},
{
id: 2,
name: 'Level 1 - B',
nodes: []
},
{
id: 3,
name: 'Level 1 - B',
nodes: [{
id: 65,
name: 'Level 2 = A',
nodes: []
},
{
id: 124,
name: 'Level 2 = A',
nodes: [{
id: 3901,
name: 'Level 3 - A'
},
{
id: 29182,
name: 'Level 3 - B',
nodes: [{
id: 32423413,
name: 'Level 4 - A'
}]
}
]
},
{
id: 534,
name: 'Level 2 = A',
nodes: []
}
]
},
];
var newObjectToInsert = {id: 999, name: 'new name'};
var nodeId = 3901;
var inserted = tree.some(function cb(v) {
var nodes = v.nodes || [];
return v.id === nodeId?
(v.nodes = nodes).push(newObjectToInsert):
nodes.some(cb);
});
console.log(tree);
body > div { min-height: 100%; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

You have a recursive structure. So you visit all items you need a visitor.
Pseudo code:
function visit(visitor, tree) {
visitor(tree);
items.nodes.forEach(subTree => visit(visitor, subTree));
}
And use
visit(node => {
if (node.id === 'whatever'){
node.push({yournode});
}
}, tree);

Related

Filter unknown deep nested array based on input value

First I must say sorry if this question is already answered, but I have not found the answer I am looking for :(
I have an array of objects with unknown nesting depth (it can be 20-30 or even more) and I want to filter it's 'name' property based on input field value.
public nestedArray = [
{id: 1, name: 'Example_1', children: []},
{id: 2, name: 'Test', children: []},
{id: 3, name: 'Test Name', children: [
{id: 10, name: 'Child name', children: [
{id: 20, name: 'Example_14', children: []},
{id: 30, name: 'Last Child', children: []}
]
}
]
}
];
The result I want to receive is an array of objects with only one level deep with 'name' field which includes input value.
For example my input value is 'am', so the result would be:
resultsArray = [
{id: 1, name: 'Example_1'},
{id: 3, name: 'Test Name'},
{id: 10, name: 'Child name'},
{id: 20, name: 'Example_14'}
];
There is no problem to do it on the first level like that:
public filter(array: any[], input_value: string): void {
array = array.filter(el => {
return el.name.toLowerCase().includes(input_value.toLowerCase()));
}
}
Thanks in advance!
You could map the array and their children and take a flat result of objects where the string is matching the name property.
const
find = value => ({ children, ...o }) => [
...(o.name.includes(value) ? [o] : []),
...children.flatMap(find(value))
],
data = [{ id: 1, name: 'Example_1', children: [] }, { id: 2, name: 'Test', children: [] }, { id: 3, name: 'Test Name', children: [{ id: 10, name: 'Child name', children: [{ id: 20, name: 'Example_14', children: [] }, { id: 30, name: 'Last Child', children: [] }] }] }],
result = data.flatMap(find('am'));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another solution with a single result array and a classic approach.
const
find = (array, value) => {
const
iter = array => {
for (const { children, ...o } of array) {
if (o.name.includes(value)) result.push(o);
iter(children);
}
},
result = [];
iter(array);
return result;
},
data = [{ id: 1, name: 'Example_1', children: [] }, { id: 2, name: 'Test', children: [] }, { id: 3, name: 'Test Name', children: [{ id: 10, name: 'Child name', children: [{ id: 20, name: 'Example_14', children: [] }, { id: 30, name: 'Last Child', children: [] }] }] }],
result = find(data, 'am');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Iterate over Array of Objects with Conditions

My Array of objects looks like this:
const categories = [{
id: 1,
name: 'level 1'
}, {
id: 2,
name: 'level 2'
}, {
id: 3,
name: 'level 3'
}];
I want to dynamically iterate over this categories array and output the name property as a string, separated with commas except the first object in the array
const output = 'level 2, level 3';
categories could potentially contain several objects.
const categories = [{
id: 1,
name: 'level 1'
}, {
id: 2,
name: 'level 2'
}, {
id: 3,
name: 'level 3'
}, ..., ..., ...];
however, it is important that the first object is not outputted
const categories = [{id: 1, name: 'level 1'}];
const output = '';
This is quite trivial
const getNames = arr => arr.map(item => item.name).slice(1).join(", "); // or slice first, map later
const categories1 = [{
id: 1,
name: 'level 1'
}, {
id: 2,
name: 'level 2'
}, {
id: 3,
name: 'level 3'
}];
const categories2 = [{
id: 1,
name: 'level 1'
}];
console.log("1:", getNames(categories1))
console.log("2:", getNames(categories2))
You could exclude first element by combining the use of destructing assignment and spread operator
const categories = [
{ id: 1, name: 'level 1' },
{ id: 2, name: 'level 2' },
{ id: 3, name: 'level 3' }
]
const [first, ...rest] = categories
const res = rest.map(cat => cat.name).join(', ')
console.log(res)
const categories = [{
id: 1,
name: 'level 1'
}, {
id: 2,
name: 'level 2'
}, {
id: 3,
name: 'level 3'
}];
const output = categories.reduce((ans, v, i) => {
return i === 1 ? v.name : (ans + ',' + v.name)
});
console.log(output)
This can be done in several ways. However, using Array.prototype.map() combined with Array.prototype.join() and Array.prototype.slice() is the easiest way.
const categories = [
{ id: 1, name: 'level 1' },
{ id: 2, name: 'level 2' },
{ id: 3, name: 'level 3' }
];
categories.slice(1).map(category => category.name).join(', ');
P.S.
using slice at first will make a new array starting from index 1 (second object)
using map will fill the new array with the value of "name" properties of all the objects in the array.
Finally, using join will convert the array to a string that consists of all the values in the array separated with the value you provide between the parentheses.

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

How can I concat two arrays of objects in a tree structure?

I have 2 arrays of objects
The first array
[
{
value: 'Node 1',
id: 1,
childs: [
{
value: 'Node 2',
id : 2,
childs: [
{
value: 'Node 3',
id: 3
},
{
value: 'Node 4',
id: 4
}
]
}
]
}
]
and the second array
[
{
value: 'Node 1',
id: 1,
childs: [
{
value: 'Node 5',
id : 5
}
]
}
]
and i don't understand how can i concat these arrays of object in a tree structure.
I need this result
[
{
value: 'Node 1',
id: 1,
childs: [
{
value: 'Node 2',
id : 2,
childs: [
{
value: 'Node 3',
id: 3
},
{
value: 'Node 4',
id: 4
}
]
},
{
value: 'Node 5',
id : 5
}
]
}
]
and these arrays can be more difficult and have more childs.
How can i get what i want?
Try this Array#forEach function and Array#filter
var arr = [ { value: 'Node 1', id: 1, childs: [ { value: 'Node 2', id : 2, childs: [ { value: 'Node 3', id: 3 }, { value: 'Node 4', id: 4
} ] } ] } ];
var arr2= [ { value: 'Node 1', id: 1, childs: [ { value: 'Node 5', id : 5 } ] } ]
arr.forEach(function(a){
var k =arr2.filter(i=> a.value == i.value);
a.childs.push(...k[0].childs)
})
console.log(arr)
You could use an iterative and recursive approach by hashing the id for the actual level.
function update(target, source) {
var hash = Object.create(null);
target.forEach(function (o) {
hash[o.id] = o;
});
source.forEach(function (o, i, a) {
if (hash[o.id]) {
o.children && update(hash[o.id].children = hash[o.id].children || [], o.children)
} else {
target.push(o);
}
});
}
var array1 = [{ value: 'Node 1', id: 1, children: [{ value: 'Node 2', id: 2, children: [{ value: 'Node 3', id: 3 }, { value: 'Node 4', id: 4 }] }] }],
array2 = [{ value: 'Node 1', id: 1, children: [{ value: 'Node 5', id: 5 }] }];
update(array1, array2);
console.log(array1);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You're looking for the concat() method. See this.
So your code should look like this:
array1[0].children.concat(array2[0].children);

Merging Arrays of Objects by Key/Values

I have two separate arrays of objects that I need to merge based if a specific key value matches. Might make more sense after analyzing the data:
Array 1
let categories = [
{ id: 5, slug: 'category-5', items: [] },
{ id: 4, slug: 'category-4', items: [] },
{ id: 3, slug: 'category-3', items: [] },
]
Array 2
let items = [
{ id: 5, data: [{ title: 'item title', description: 'item description' }] },
{ id: 5, data: [{ title: 'item title 2', description: 'item description 2' }] },
{ id: 4, data: [{ title: 'item title 4', description: 'item description 4' }] },
]
Expected Output
let mergedOutput = [
{ id: 5, slug: 'category-5',
items: [
{ title: 'item title', description: 'item description' },
{ title: 'item title 2', description: 'item description 2' }
]
},
{ id: 4, slug: 'category-4',
items: [
{ title: 'item title 4', description: 'item description 4' },
]
},
{ id: 3, slug: 'category-3', items: [] },
]
So....I need to add Array 2 to Array 1 if their id's match.
Array 1 will stay the same, but if Array 2 matches, the items property of Array 1 (empty) will be replaced by the data property of Array 2
I know this is a pretty basic / and redundant question, but I can't find the resources for my use case / object structure.
I was able to easily group arrays with lodash -- so if there is a similar solution with that library -- that would good! Or just some direction would suffice.
Thanks in advance!
You can loop first array and then use filter to get objects with same id as current element and add that items to current object.
let categories = [
{ id: 5, slug: 'category-5', items: [] },
{ id: 4, slug: 'category-4', items: [] },
{ id: 3, slug: 'category-3', items: [] },
]
let items = [
{ id: 5, data: [{ title: 'item title', description: 'item description' }] },
{ id: 5, data: [{ title: 'item title 2', description: 'item description 2' }] },
{ id: 4, data: [{ title: 'item title 4', description: 'item description 4' }] },
]
categories.forEach(function(e) {
var i = items.filter(a => a.id == e.id).map(a => a.data);
e.items = i;
})
console.log(categories)
You could reduce the items into categories:
let res = items.reduce((a, b) => {
let it = a.find(e => e.id === b.id);
if (! it) return a;
it.items = it.items.concat(b.data);
return a;
}, categories);
let categories = [{
id: 5,
slug: 'category-5',
items: []
},
{
id: 4,
slug: 'category-4',
items: []
},
{
id: 3,
slug: 'category-3',
items: []
},
];
let items = [{
id: 5,
data: [{
title: 'item title',
description: 'item description'
}]
},
{
id: 5,
data: [{
title: 'item title 2',
description: 'item description 2'
}]
},
{
id: 4,
data: [{
title: 'item title 4',
description: 'item description 4'
}]
},
];
let res = items.reduce((a, b) => {
let it = a.find(e => e.id === b.id);
if (! it) return a;
it.items = it.items.concat(b.data);
return a;
}, categories);
console.log(res);
It might be faster to get the ids in an object first, so we don't have to use find on the same id many times:
function merge(result, toMerge, mergeInto) {
let i = 0, hm = {};
for (let {id} of categories) {
hm[id] = i;
i++;
}
return toMerge.reduce((a,b) => {
let it = a[hm[b.id]];
if (!it) return a;
it[mergeInto] = it[mergeInto].concat(b.data);
return a;
}, result);
}
let categories = [
{ id: 5, slug: 'category-5', items: [] },
{ id: 4, slug: 'category-4', items: [] },
{ id: 3, slug: 'category-3', items: [] },
];
let items = [
{ id: 5, data: [{ title: 'item title', description: 'item description' }] },
{ id: 5, data: [{ title: 'item title 2', description: 'item description 2' }] },
{ id: 4, data: [{ title: 'item title 4', description: 'item description 4' }] },
];
function merge(result, toMerge, mergeInto) {
let i = 0, hm = {};
for (let {id} of categories) {
hm[id] = i;
i++;
}
return toMerge.reduce((a,b) => {
let it = result[hm[b.id]];
if (!it) return a;
it[mergeInto] = it[mergeInto].concat(b.data);
return a;
}, result);
}
console.log(merge(categories, items, 'items'));
I would make the categories as hash map and the key would be the id and iterate over all the items only.
then you get O(N) solution.

Categories

Resources