Group array of nested objects with multiple levels in JavaScript - javascript

I'm trying to group a big nested object with multiple properties such as this one :
[
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 70,
"name": "Name70"
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 61,
"name": "Name61"
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 4,
"name": "Name4",
"sub": {
"id": 5,
"name": "Name5",
"sub": {
"id": 29,
"name": "Name29"
}
}
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 4,
"name": "Name4",
"sub": {
"id": 5,
"name": "Name5",
"sub": {
"id": 8,
"name": "Name8",
"sub": {
"id": 163,
"name": "Name163"
}
}
}
}
},
{
"id": 10,
"name": "Name10",
"sub": {
"id": 4,
"name": "Name4"
}
}
]
As you can see, the "sub" are not arrays as of now, but they would be in the expected output even if there's only one object in it.
I'd like to group the array by object's id recursively to get this kind of output :
[
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": [
{
"id": 70,
"name": "Name70"
},
{
"id": 61,
"name": "Name61"
},
{
"id": 4,
"name": "Name4",
"sub": [
{
"id": 5,
"name": "Name5",
"sub": [
{
"id": 29,
"name": "Name29"
},
{
"id": 8,
"name": "Name8",
"sub": [
{
"id": 163,
"name": "Name163"
}
]
}
]
}
]
}
]
},
{
"id": 10,
"name": "Name10",
"sub": [
{
"id": 4,
"name": "Name4"
}
]
}
]
So far, I tried some shenanigans with lodash and d3.nest() but I just can't seem to group it.
Have you guys ever face something similar? And if so, how did you manage to code this?
Thanks a lot

You could take a recursive approach with a function which merges an object into an array by looking for same id.
const
merge = (target, { sub, ...o }) => {
let temp = target.find(({ id }) => id === o.id);
if (sub) sub = merge(temp?.sub || [], sub)
if (!temp) target.push(temp = { ...o, sub });
return target;
};
var data = [{ id: 14, name: "Name14", theme: true, sub: { id: 70, name: "Name70" } }, { id: 14, name: "Name14", theme: true, sub: { id: 61, name: "Name61" } }, { id: 14, name: "Name14", theme: true, sub: { id: 4, name: "Name4", sub: { id: 5, name: "Name5", sub: { id: 29, name: "Name29" } } } }, { id: 14, name: "Name14", theme: true, sub: { id: 4, name: "Name4", sub: { id: 5, name: "Name5", sub: { id: 8, name: "Name8", sub: { id: 163, name: "Name163" } } } } }, { id: 10, name: "Name10", sub: { id: 4, name: "Name4" } }],
result = data.reduce(merge, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You could create a Map for each sub property -- keyed by id -- and merge the objects into those maps. Then finally convert those sub-maps back to sub-arrays:
function mergeArray(arr) {
function mergeObject(a, b) {
while (b = b.sub) {
if (!a.sub) a.sub = new Map;
let child = a.sub.get(b.id);
if (child) a = child;
else a.sub.set(b.id, a = { id: b.id, name: b.name });
}
}
function convertMap(map) {
return Array.from(map.values(), obj => {
if (obj.sub) obj.sub = convertMap(obj.sub);
return obj;
});
}
let map = new Map(arr.map(({id, name}) => [id, {id, name}]));
for (let item of arr) mergeObject(map.get(item.id), item);
return convertMap(map);
}
// Demo with input from question
let input = [{"id": 14,"name": "Name14","theme": true,"sub": {"id": 70,"name": "Name70"}},{"id": 14,"name": "Name14","theme": true,"sub": {"id": 61,"name": "Name61"}},{"id": 14,"name": "Name14","theme": true,"sub": {"id": 4,"name": "Name4","sub": {"id": 5,"name": "Name5","sub": {"id": 29,"name": "Name29"}}}},{"id": 14,"name": "Name14","theme": true,"sub": {"id": 4,"name": "Name4","sub": {"id": 5,"name": "Name5","sub": {"id": 8,"name": "Name8","sub": {"id": 163,"name": "Name163"}}}}},{"id": 10,"name": "Name10","sub": {"id": 4,"name": "Name4"}}];
console.log(mergeArray(input));

I would do it with recursive functions, because I think those are more versatile:
const data = [{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 70,
"name": "Name70"
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 61,
"name": "Name61"
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 4,
"name": "Name4",
"sub": {
"id": 5,
"name": "Name5",
"sub": {
"id": 29,
"name": "Name29"
}
}
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 4,
"name": "Name4",
"sub": {
"id": 5,
"name": "Name5",
"sub": {
"id": 8,
"name": "Name8",
"sub": {
"id": 163,
"name": "Name163"
}
}
}
}
},
{
"id": 10,
"name": "Name10",
"sub": {
"id": 4,
"name": "Name4"
}
}
]
// setting up arrays
const recursiveModify = (node) => {
if (typeof node.sub === "undefined") {
return node
} else {
node.sub = [recursiveModify(node.sub)]
return node
}
}
const recursiveReduce = (nodes) => {
return nodes.reduce((a, c) => {
const item = a.find(e => e.id === c.id)
if (!item) {
a.push(c)
} else {
item.sub = recursiveReduce([...item.sub, ...c.sub])
}
return a
}, [])
}
const dataWithArray = data.map(node => {
return recursiveModify(node)
})
const result = recursiveReduce(dataWithArray)
console.log(result)
Unfortunately I could only do it with two passes - one for creating the sub as arrays, then one for actually grouping the data. I'm pretty sure it can be done in one pass - I just have no more time to work it out.

Related

React - Filter Multidimensional array with another array

I want to return only matches results.
My array:
products: [
{
"id": 1,
"name": "Product 1",
"concepts": [
{
"id": 10,
"name": "Blabla"
},
{
"id": 15,
"name": "Zlazla"
}
]
},
{
"id": 2,
"name": "Product 2",
"concepts": [
{
"id": 14,
"name": "Gulagula"
},
{
"id": 15,
"name": "Zlazla"
}
]
}
]
I want to filter products which only have one of the concepts below.
concepts array:
['14', '15']
Couldn't solve this in an easy way.
You can try this way:
var products= [
{
"id": 1,
"name": "Product 1",
"concepts": [
{
"id": 10,
"name": "Blabla"
},
{
"id": 15,
"name": "Zlazla"
}
]
},
{
"id": 2,
"name": "Product 2",
"concepts": [
{
"id": 14,
"name": "Gulagula"
},
{
"id": 15,
"name": "Zlazla"
}
]
}
]
var products = products.filter((product) => product.concepts = product.concepts.filter( (x) => x.id == 14 || x.id == 15));
console.log(products);

How to get immediate parent Id of the child id in array of nested json Object?

I need to get the parent id of the specific child.
Here is my sample JSON, If I give entity id 32 it should return 6 as parent Id and if I give 30 it should return 5 as parent id.
const arr = [{
"id": 0,
"name": "My Entity",
"children": [
{
"id": 1,
"name": "MARKET",
"children": [
{
"id": 2,
"name": "Sales",
"children": [
{
"id": 3,
"name": "District 1",
"children": [
{
"id": 5,
"name": "Area 1",
"children": [
{
"entityId": 30,
"id": 26,
"name": "Mumbai"
},
{
"entityId": 31,
"id": 26,
"name": "Hyderabad"
}
],
"num": 0,
},
{
"id": 6,
"name": "Area 2",
"children": [
{
"entityId": 32,
"id": 32,
"name": "Karnataka"
},
{
"entityId": 33,
"id": 33,
"name": "Andhra Pradesh"
}
],
"num": 0,
},
]
},
]
},
]
},
]
}]
Here is the code I have tried
const findParent = (arr, entityId) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i].entityId === entityId) {
return [];
} else if (arr[i].children && arr[i].children.length) {
const t = findParents(arr[i].children, entityId);
if (t !== false) {
t.push(arr[i].id);
return t;
}
}
}
return false;
};
findParents(arr, 30);
But it is returning like below
[
5,
3,
2,
1,
0
]
But I want the output to be
[
5
]
Kindly help me on this, thanks
Replace this:
t.push(arr[i].id);
with:
if (t.length == 0) t.push(arr[i].id);
I would suggest easier solution
const findParent = (arr, entityId) => {
const children = arr.flatMap(parent =>
(item.children || []).map(child => ({ parent, child, entityId: child.entityId }))
)
const res = children.find(item => item.entityId === entityId)
return res.entityId || findChildren(res.map(v => v.child), entityId)
}

Filtering an array based on multiple arrays inputs

I have three arrays.
1. Existing viewers array - existingViewers
New viewers array - newViewers
Permitted Viewers array - permittedViewers
permittedViewers is used for rendering the drop-down. And I wish to filter the newViewers and existingViewers entries from the permittedViewers.
I am doing this as three steps. And I am afraid this is not the optimized way. Can someone suggest the ideal way of doing this?
The expected result is
[
{
"id": 4,
"name": "name4"
},
{
"id": 5,
"name": "name5"
},
{
"id": 6,
"name": "name6"
}
]
let existingViewers = [{
"viewerId": 1,
"name": "name1"
},
{
"viewerId": 2,
"name": "name2"
}
],
newViewers = [
{
"viewerId": 3,
"name": "name3"
}
],
permittedViewers = [{
"id": 1,
"name": "name1"
},
{
"id": 2,
"name": "name2"
},
{
"id": 3,
"name": "name3"
},
{
"id": 4,
"name": "name4"
},
{
"id": 5,
"name": "name5"
},
{
"id": 6,
"name": "name6"
}
]
let grouped = [...existingViewers, ...newViewers]
let viewerFilter = grouped.map(viewer => { return viewer.viewerId; });
let filteredPermittedViewers = permittedViewers.filter(viewer => !viewerFilter.includes(viewer.id));
console.log(filteredPermittedViewers)
I'd make a Set of the ids of the first two arrays, and then filter the third by whether the set includes the id. (Sets have O(1) lookup time)
let existingViewers=[{"viewerId":1,"name":"name1"},{"viewerId":2,"name":"name2"}],newViewers=[{"viewerId":3,"name":"name3"}],permittedViewers=[{"id":1,"name":"name1"},{"id":2,"name":"name2"},{"id":3,"name":"name3"},{"id":4,"name":"name4"},{"id":5,"name":"name5"},{"id":6,"name":"name6"}];
const ids = new Set([...existingViewers, ...newViewers].map(({ viewerId }) => viewerId));
const output = permittedViewers.filter(({ id }) => !ids.has(id));
console.log(output);
You can compress all three statements into a single statement -- just replace the variable name with the statement that creates it:
let existingViewers = [{
"viewerId": 1,
"name": "name1"
},
{
"viewerId": 2,
"name": "name2"
}
],
newViewers = [
{
"viewerId": 3,
"name": "name3"
}
],
permittedViewers = [{
"id": 1,
"name": "name1"
},
{
"id": 2,
"name": "name2"
},
{
"id": 3,
"name": "name3"
},
{
"id": 4,
"name": "name4"
},
{
"id": 5,
"name": "name5"
},
{
"id": 6,
"name": "name6"
}
]
let filteredPermittedViewers = permittedViewers.filter(viewer => ! [...existingViewers, ...newViewers].map(viewer => viewer.viewerId).includes(viewer.id));
console.log(filteredPermittedViewers)

Find the parent of the searched value using javascript

I have a JSON data in the following format which needs to be filtered based on a specific value :
[
{
"id": 0,
"name": "ROOT-0",
"childs": [
{
"id": 1,
"name": "ROOT-1",
"childs": [
{
"id": 11,
"name": "ROOT-11",
},
{
"id": 12,
"name": "ROOT-12",
},
]
},
{
"id": 2,
"name": "ROOT-2",
"childs": [
{
"id": 21,
"name": "ROOT-21",
},
{
"id": 22,
"name": "ROOT-22",
},
]
},
{
"id": 3,
"name": "ROOT-3",
"childs": [
{
"id": 31,
"name": "ROOT-31",
},
{
"id": 32,
"name": "ROOT-32",
},
]
}
]
}]
The scenario is that I need to get ROOT-1 as final result if I look for ROOT-11/ROOT-12.
I have tried filtering with this following code
var res = data[0].filter(function f(o) {
if (o.name.includes("ROOT-11")) return o;
})
But I am not able to get a grip on the logic. Is there a way to achieve my desired output
You could use find()...
var result = data[0].childs.find(x => {
return x.childs.find(y => {
return y.name === name;
});
}).name;
Or you could write a function...
function findParentName(name, data) {
return data[0].childs.find(x => {
return x.childs.find(y => {
return y.name === name;
});
}).name;
}
var result = findParentName('ROOT-11', data);
console.log(result);
Doing this will give you the best performance result as find() will return as soon as it finds a match, and not iterate through each remaining loop like forEach() or map()
If you're using ES6 you can say...
const result = data[0].childs.find(x => x.childs.find(y => y.name === 'ROOT-11')).name;
You can grab the item using a few filters and a find, to get the result you are looking for:
let items = [{
"id": 0,
"name": "ROOT-0",
"childs": [{
"id": 1,
"name": "ROOT-1",
"childs": [{
"id": 11,
"name": "ROOT-11",
},
{
"id": 12,
"name": "ROOT-12",
},
]
},
{
"id": 2,
"name": "ROOT-2",
"childs": [{
"id": 21,
"name": "ROOT-21",
},
{
"id": 22,
"name": "ROOT-22",
},
]
},
{
"id": 3,
"name": "ROOT-3",
"childs": [{
"id": 31,
"name": "ROOT-31",
},
{
"id": 32,
"name": "ROOT-32",
},
]
}
]
}]
function find(name) {
let result
items.filter(item =>
result = item.childs.find(item2 =>
item2.childs.filter(i => i.name == name).length > 0
)
)
return result.name || ''
}
console.log(find('ROOT-11'))
console.log(find('ROOT-22'))
console.log(find('ROOT-32'))
You could, for an arbitrary count nested children, use a recusion approach by iterating the actual level and if not found check the children with the actual name.
If the wanted name is found, the parent's name is handed over through all nested calls and returned.
function getParent(array, search, parent) {
return array.some(o => o.name === search || o.children && (parent = getParent(o.children, search, o.name)))
&& parent;
}
var data = [{ id: 0, name: "ROOT-0", children: [{ id: 1, name: "ROOT-1", children: [{ id: 11, name: "ROOT-11" }, { id: 12, name: "ROOT-12" }] }, { id: 2, name: "ROOT-2", children: [{ id: 21, name: "ROOT-21" }, { id: 22, name: "ROOT-22" }] }, { id: 3, name: "ROOT-3", children: [{ id: 31, name: "ROOT-31" }, { id: 32, name: "ROOT-32" }] }] }]
console.log(getParent(data, 'ROOT-0')); // undefined no parent found
console.log(getParent(data, 'ROOT-1')); // ROOT-0
console.log(getParent(data, 'ROOT-11')); // ROOT-1
console.log(getParent(data, 'ROOT-31')); // ROOT-3
.as-console-wrapper { max-height: 100% !important; top: 0; }

Making one to many relation array in JavaScript

I have two arrays of objects:
var a = [{
"id": 1,
"name": "q"
},
{
"id": 2,
"name": "l"
}]
and another is
var b = [{
"id": 3,
"sub": 1,
"name": "ni"
},
{
"id": 4,
"sub": 2,
"name": "bh"
}]
Here sub is the id in a
I need to have a new array which will look like this:
var c = [
{
"id":1,
"name":"q",
"map":[
{
"id":3,
"name":"ni"
}
]
},
{
"id":2,
"name":"l",
"map":[
{
"id":4,
"name":"bh"
}
]
}
]
How can I do that in JavaScript?
I am using underscore in my project.
In plain Javascript you could use Array#map, Array#forEach and a hash table.
var a = [{ "id": 1, "name": "q" }, { "id": 2, "name": "l" }],
b = [{ "id": 3, "sub": 1, "name": "ni" }, { "id": 4, "sub": 2, "name": "bh" }],
hash = Object.create(null),
result = a.map(function (a, i) {
hash[a.id] = { id: a.id, name: a.name };
return hash[a.id];
}, hash);
b.forEach(function (a) {
hash[a.sub].map = hash[a.sub].map || [];
hash[a.sub].map.push({ id: a.id, name: a.name });
}, hash);
console.log(result);
ES6
var a = [{ "id": 1, "name": "q" }, { "id": 2, "name": "l" }],
b = [{ "id": 3, "sub": 1, "name": "ni" }, { "id": 4, "sub": 2, "name": "bh" }],
hash = Object.create(null),
result = a.map((hash => a => hash[a.id] = { id: a.id, name: a.name, map: [] })(hash));
b.forEach((hash => a => hash[a.sub].map.push({ id: a.id, name: a.name }))(hash));
console.log(result);
You can do it with the help of map function and then use the find function to search the data from the other array.
var b = [{
"id": 3,
"sub": 1,
"name": "ni"
}, {
"id": 4,
"sub": 2,
"name": "bh"
}];
var a = [{
"id": 1,
"name": "q"
}, {
"id": 2,
"name": "l"
}];
var final = _.map(b, function(d) {
return {
id: d.name,
name: d.name,
map: _.find(a, function(adata) {
return adata.id == d.sub; //use underscore find to get the relevant data from array a
})
}
});
console.log(final);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Categories

Resources