Making one to many relation array in JavaScript - 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>

Related

Group array of nested objects with multiple levels in 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.

Map Json Data to new key-value pair JavaScript object

I want to map one json data to a new javascript object like following. Here the json data is dynamic and can have more files with more users. The group information is new and it depends on parent-child information. Can anyone please help me out? Thank you for your time.
Before:
{
"userinfo": {
"/home/user/main/sub/info/1stfile.txt": {
"John": "something",
"Mike": "something",
"Merry": "something",
"Susan": "something"
},
"/home/user/main/info/2ndfile.txt": {
"Mulan": "something",
"James": "something"
},
"/home/user/main/info/3rdfile.txt": {
"Nancy": "something"
},
"/home/user/main/4thfile.txt": {
"Kamal": "something",
"Xian": "something",
"Mila": "something"
}
}
}
After:
{
"name": "main",
"children": [
{
"name": "1stfile",
"children": [
{
"name": "John",
"group": "1"
},
{
"name": "Mike",
"group": "1"
},
{
"name": "Merry",
"group": "1"
},
{
"name": "Susan",
"group": "1"
}
],
"group": 1
},
{
"name": "2ndfile",
"children": [
{
"name": "Mulan",
"group": 2
},
{
"name": "James",
"group": 2
}
],
"group": 2
},
{
"name": "3rdfile",
"children": [
{
"name": "Nancy",
"group": 3
}
],
"group": 3
},
{
"name": "4thfile",
"children": [
{
"name": "Kamal",
"group": 4
},
{
"name": "Xian",
"group": 4
},
{
"name": "Mila",
"group": 4
}
],
"group": 4
}
],
"group": 0
}
I was trying to build one block of parent-child by using following code
var jsonData = json["userinfo"];
var keys = Object.keys(jsonData);
console.log(keys);
let data = {};
for (var j = 0; j < keys.length; j++) {
let g = 1;
data[j] = { name: keys[j], group: g++ };
}
console.log(data);
Which is giving following output
{
0: {
"name": "/home/user/main/sub/info/1stfile.txt",
"group": 1
},
1: {
"name": "/home/user/main/info/2ndfile.txt",
"group": 1
},
2: {
"name": "/home/user/main/info/3rdfile.txt",
"group": 1
},
3: {
"name": "/home/user/main/4thfile.txt",
"group": 1
}
}
The value is assigning properly but is creating extra keys (0,1,2,3)!
Assuming you need something like this.
You can utilize Array.map() and Object.keys() function for your operation.
<script>
const beforeJSON = `{
"userinfo": {
"/home/user/main/sub/info/1stfile.txt": {
"John": "something",
"Mike": "something",
"Merry": "something",
"Susan": "something"
},
"/home/user/main/info/2ndfile.txt": {
"Mulan": "something",
"James": "something"
},
"/home/user/main/info/3rdfile.txt": {
"Nancy": "something"
},
"/home/user/main/4thfile.txt": {
"Kamal": "something",
"Xian": "something",
"Mila": "something"
}
}
}`
const before = JSON.parse(beforeJSON);
const filenames = Object.keys(before.userinfo);
const after = {
name: 'main',
children: [],
group: 0,
}
const children = filenames.map((filename, idx) => {
const innerChildren = Object.keys(before.userinfo[filename]).map((n) => ({
name: n,
group: idx + 1,
}))
return ({
name: filename,
children: innerChildren,
group: idx + 1,
});
})
after.children = children;
console.log(after);
</script>
Please format your code next time before posting another question.

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; }

lodash merge and combine objects

I have an array of objects as below that I read from my database using sequelize ORM:
I want to have all my videos from a section, but the better I can return using sequelize is :
[{
"id": 2,
"name": "Ru",
"subsection": 1,
"Video": {
"id": 11,
"source": "sourrrccrsss22222",
"videoSubSection": 2
}
},
{
"id": 2,
"name": "Ru",
"subsection": 1,
"Video": {
"id": 12,
"source": "sourrrccrsss111",
"videoSubSection": 2
}
},
{
"id": 1,
"name": "Oc",
"subsection": 1,
"Video": {
"id": 13,
"source": "sourrrcc",
"videoSubSection": 1
}
},
{
"id": 1,
"name": "Oc",
"subsection": 1,
"Video": {
"id": 14,
"source": "sourrrcc",
"videoSubSection": 1
}
}]
Is there a way to merge and combine the objects in my array to obtain something like this :
[{
"id": 2,
"name": "Ru",
"subsection": 1,
"Video": [{
"id": 11,
"source": "sourrrccrsss22222",
"videoSubSection": 2
},{
"id": 12,
"source": "sourrrccrsss111",
"videoSubSection": 2
}]
},
{
"id": 1,
"name": "Oc",
"subsection": 1,
"Video": [{
"id": 13,
"source": "sourrrcc",
"videoSubSection": 1
},{
"id": 14,
"source": "sourrrcc",
"videoSubSection": 1
}]
}
The function that approach the most is _.mergeWith(object, sources, customizer) but the main problem I have is that I have on object and need to merge this object.
In plain Javascript, you can use Array#forEach() with a temporary object for the arrays.
var data = [{ id: 2, name: "Ru", subsection: 1, Video: { id: 11, source: "sourrrccrsss22222", VideoSubSection: 2 } }, { id: 2, name: "Ru", subsection: 1, Video: { id: 12, source: "sourrrccrsss111", VideoSubSection: 2 } }, { id: 1, name: "Oc", subsection: 1, Video: { id: 13, source: "sourrrcc", VideoSubSection: 1 } }, { id: 1, name: "Oc", subsection: 1, Video: { id: 14, source: "sourrrcc", VideoSubSection: 1 } }],
merged = function (data) {
var r = [], o = {};
data.forEach(function (a) {
if (!(a.id in o)) {
o[a.id] = [];
r.push({ id: a.id, name: a.name, subsection: a.subsection, Video: o[a.id] });
}
o[a.id].push(a.Video);
});
return r;
}(data);
document.write('<pre>' + JSON.stringify(merged, 0, 4) + '</pre>');
Maybe try transform():
_.transform(data, (result, item) => {
let found;
if ((found = _.find(result, { id: item.id }))) {
found.Video.push(item.Video);
} else {
result.push(_.defaults({ Video: [ item.Video ] }, item));
}
}, []);
Using reduce() would work here as well, but transform() is less verbose.
You can do it this way (test is your db output here)
var result = [];
var map = [];
_.forEach(test, (o) => {
var temp = _.clone(o);
delete o.Video;
if (!_.some(map, o)) {
result.push(_.extend(o, {Video: [temp.Video]}));
map.push(o);
} else {
var index = _.findIndex(map, o);
result[index].Video.push(temp.Video);
}
});
console.log(result); // outputs what you want.

Categories

Resources