javascript optimize multiple reduce and map to extract data from nested objects - javascript

I am extracting some data out of an array of nested objects, using two reducees, and map, which is working at the moment, but it is a bit ugly. How can this be optimized?
function extractSchools(schools) {
let schoolData = [];
if (schools) {
schoolData = schools.reduce(function(parentdata, chlrn) {
let childrenlist = chlrn.children;
let childrendata = [];
if (childrenlist) {
childrendata = childrenlist.reduce(function(addrsslist, school) {
return addrsslist.concat(school.address.map(i => i.school));
}, []);
}
return parentdata.concat(chlrn.parent, childrendata);
}, []);
}
return {
schoolData
};
}
const schools = [{
"parent": "Thomas Jefferson",
"children": [{
"address": [{
"school": "School A"
}]
},
{
"address": [{
"school": "School B"
}]
}
]
},
{
"parent": "Jack Chan",
"children": [{
"address": [{
"school": "School C"
}]
}]
}
];
console.log(extractSchools(schools));
How can I optimize this function to get the same results? using one reduce instead of two... or some other optimal way of doing it.

You can remove the if (childrenlist) { and use a pre-filter.
function extractSchools(schools) {
let schoolData = [];
if (schools) {
schoolData = schools
.filter(data => data.children)
.reduce((parentdata, chlrn) => {
const childrendata = chlrn.children.reduce(
(addrsslist, school) =>
addrsslist.concat(school.address.map(i => i.school)),
[]
);
return parentdata.concat(chlrn.parent, childrendata);
}, []);
}
return { schoolData };
}
const schools = [
{
parent: "Thomas Jefferson",
children: [
{
address: [
{
school: "School A"
}
]
},
{
address: [
{
school: "School B"
}
]
}
]
},
{
parent: "Jack Chan",
children: [
{
address: [
{
school: "School C"
}
]
}
]
}
];
console.log(extractSchools(schools));

Try this, the result is little different than what you are expecting, but this will be a more generic way where you will have addresses with respect to school.
schools.map(p => {
return {[p.parent]: p.children.map(c => c.address.map(add => add.school))}
})
[
{
"Thomas Jefferson": [
[
"School A"
],
[
"School B"
]
]
},
{
"Jack Chan": [
[
"School C"
]
]
}
]

Related

Convert JSON array with nested arrays (tree) to flat JSON array [duplicate]

This question already has answers here:
Find all values by specific key in a deep nested object
(11 answers)
Closed 10 months ago.
I have this JSON array tree that can include any number of nested arrays:
const namesArrayTree = [
{
"name": "Peter"
},
{
"name": "folder1",
"isArray": true,
"namesArray": [
{
"name": "Paul"
},
{
"name": "folder2",
"isArray": true,
"namesArray": [
{
"name": "Mary"
},
{
"name": "John"
}
]
}
]
},
{
"name": "Mark"
}
]
I need to transform it to a flat array including only the names:
const namesArrayFlat = [ "Peter", "Paul", "Mary", "John", "Mark" ]
So I'm using this code to do the transformation:
const namesArrayTree = [
{
"name": "Peter"
},
{
"name": "folder1",
"isArray": true,
"namesArray": [
{
"name": "Paul"
},
{
"name": "folder2",
"isArray": true,
"namesArray": [
{
"name": "Mary"
},
{
"name": "John"
}
]
}
]
},
{
"name": "Mark"
}
] ;
function getNamesList(item) {
let name = item.name;
let isArray = item.isArray;
if (isArray) {
name = item.namesArray.map(getNamesList).join("\r\n");
}
return name;
}
const namesList = namesArrayTree.map(getNamesList).join("\r\n");
const namesArrayFlat = namesList.split("\r\n");
console.log(namesArrayFlat)
The code works well, but I would like to get rid of the extra steps to create a list with the names using join.("\r\n") and then convert to array using split("\r\n").
That is, I would like to reduce the code by removing the following:
function getNamesList(item) {
let name = item.name;
let isArray = item.isArray;
if (isArray) {
/* remove code to join by "\r\n" */
name = item.namesArray.map(getNamesList)
}
return name;
}
/* remove code to create "namesList" constant and remove code to join by "\r\n") */
const namesArrayFlat = namesArrayTree.map(getNamesList)
console.log(namesArrayFlat)
(The above code still returns a tree nested arrays structure)
Any ideas about how to get rid of the extra code? also any suggestions about how to improve the code would be great, thanks!
function getNamesList(item) {
return item.isArray ? item.namesArray.map(getNamesList) : item.name
}
const names = namesArrayTree.map(getNamesList).flat(Infinity)
console.log(names)
You can achieve this with an array reducer as follows:
const namesArray = [
{
"name": "Peter"
},
{
"name": "folder1",
"isArray": true,
"namesArray": [
{
"name": "Paul"
},
{
"name": "folder2",
"isArray": true,
"namesArray": [
{
"name": "Mary"
},
{
"name": "John"
}
]
}
]
},
{
"name": "Mark"
}
] ;
function reduceNamesList(list, item) {
if (item.isArray) {
return item.namesArray.reduce(reduceNamesList, list);
}
list.push(item.name)
return list
}
const namesList = namesArray.reduce(reduceNamesList, [])
console.log(namesList)

Functional immutable way in javascript to copy an array with additional items in certain positions depending on condition on items

I have an array:
[
{ "name": "batman", "hasSidekick": true },
{ "name": "shazam!", "hasSidekick": false },
{ "name": "capt america", "hasSidekick": true },
{ "name": "spiderman", "hasSidekick": false }
]
From this, I want to create a new array of hero names which will have all of the above names but when hasSidekick is true for a hero, there should be an additional name inserted after it.
Expected output:
[
"batman",
"batman's sidekick",
"shazam!", ,
"capt america",
"capt america's sidekick",
"spiderman"
]
I can do it with forEach and pushing additional items conditionally based on hasSidekick:
const heroes = [
{ name: "batman", hasSidekick: true },
{ name: "shazam!", hasSidekick: false },
{ name: "capt america", hasSidekick: true },
{ name: "spiderman", hasSidekick: false },
];
let heroesAndSidekicks = [];
heroes.forEach(hero => {
heroesAndSidekicks.push(hero.name);
if (hero.hasSidekick) {
heroesAndSidekicks.push(`${hero.name}'s sidekick`);
}
});
console.log(heroesAndSidekicks);
But please suggest how I can do it in functional programming way without mutation.
You could take Array#flatMap.
var data = [{ name: "batman", hasSidekick: true }, { name: "shazam!", hasSidekick: false }, { name: "capt america", hasSidekick: true }, { name: "spiderman", hasSidekick: false }],
result = data.flatMap(({ name, hasSidekick }) => hasSidekick
? [name, name + '\'s sidekick']
: name
);
console.log(result);
I think Array.prototype.reduce() can solve your issue. From the documentation:
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
Please find a possible solution below:
const data = [
{ "name": "batman", "hasSidekick": true },
{ "name": "shazam!", "hasSidekick": false },
{ "name": "capt america", "hasSidekick": true },
{ "name": "spiderman", "hasSidekick": false }
];
const result = data.reduce((a, e) => {
a.push(e.name);
if (e.hasSidekick) {
a.push(`${e.name}'s sidekick`);
}
return a;
}, []);
console.log(result);
I hope that helps!

how to parse array of objects inside an object using maps or forEach

my JSON data looks like this: {
"objects": [
{
"name": "case1",
"description": "description of case1",
"shapes": [
{
"shape_name": "circle"
}
],
"order_ref": [
{
"id": "2233"
}
]
},
{
"name": "case2",
"description": "description of case2",
"shapes": [
{
"shape_name": "heart"
},
{
"shape_name": "square"
}
],
"order_ref": [
{
"id": "1212"
}
]
}
]
}
I need to create a table from the data.
The table should look like this:
Id Shape Name Description
2233 Circle Case1 Desc of case1
1212 Heart Case2 Desc of case2
1212 Square Case2 Desc of case2
I tried to parse JSON data using maps: I am able to get name and description but cannot go inside shapes and order_ref. Can you suggest me a solution using maps or forEach
You can do forEach within a forEach, ex:
let array = [
{
name: "case1",
description: "description of case1",
shapes: [
{
shape_name: "circle"
}
],
order_ref: [
{
id: "2233"
}
]
},
{
name: "case2",
description: "description of case2",
shapes: [
{
shape_name: "heart"
},
{
shape_name: "square"
}
],
order_ref: [
{
id: "1212"
}
]
}
];
let newArray = [];
array.forEach(obj => {
obj.shapes.forEach(shape => {
let newObj = Object.assign({}, obj);
newObj.shape_name = shape.shape_name;
delete newObj.shapes;
newObj.id = obj.order_ref[0].id;
delete newObj.order_ref;
newArray.push(newObj);
});
});
console.log(newArray);
After you are getting the newArray, you can iterate it with and as per normal
You need two forEach() to achieve the table.
You can try the following way
var data = {
"objects": [
{
"name": "case1",
"description": "description of case1",
"shapes": [
{
"shape_name": "circle"
}
],
"order_ref": [
{
"id": "2233"
}
]
},
{
"name": "case2",
"description": "description of case2",
"shapes": [
{
"shape_name": "heart"
},
{
"shape_name": "square"
}
],
"order_ref": [
{
"id": "1212"
}
]
}
]
}
var tr = '';
data.objects.forEach(o => {
o.shapes.forEach(io => {
tr += `<tr><td>${o.order_ref[0].id}</td><td>${io.shape_name}</td><td>${o.name}</td><td>${o.description}</td></tr>`;
});
});
document.querySelector('#tableData tbody').insertAdjacentHTML('beforeend', tr);
table, th, td {
border: 1px solid black;
}
td:first-letter{
text-transform: capitalize
}
<table id="tableData">
<thead>
<th>Id</th>
<th>Shape</th>
<th>Name</th>
<th>Description</th>
</thead>
<tbody></tbody>
</table>
If you need an entry for every order ref id and shape, you can use forEach as per below:
let jsonArray = {
"objects": [
{
"name": "case1",
"description": "description of case1",
"shapes": [
{
"shape_name": "circle"
}
],
"order_ref": [
{
"id": "2233"
}
]
},
{
"name": "case2",
"description": "description of case2",
"shapes": [
{
"shape_name": "heart"
},
{
"shape_name": "square"
}
],
"order_ref": [
{
"id": "1212"
}
]
}
]
}
let output = [];
jsonArray.objects.forEach(object => {
object.order_ref.forEach(order => {
object.shapes.forEach(shape => {
output.push({
id: order.id,
shape: shape.shape_name,
name: object.name,
description: object.description
})
})
})
})

Match two object keys and display another object key value in angular 4

i have two objects like this
languages = [
{
"name": "english",
"iso_639_2_code": "eng"
},
{
"name": "esperanto",
"iso_639_2_code": "epo"
},
{
"name": "estonian",
"iso_639_2_code": "est"
}
]
and another is
user = [
{
name: "john",
language: "eng",
country: "US"
}
];
what i have to do is, match iso_639_2_code to language of user then, i have to display Language name not code from languages. basically both are different api, and i have no idea how to do it this in angular 4.
here's a link what i am trying https://stackblitz.com/edit/angular-9k2nff?file=app%2Fapp.component.ts
Use array find:
var languages = [
{"name": "english", "iso_639_2_code": "eng"},
{"name": "esperanto","iso_639_2_code": "epo"},
{"name": "estonian","iso_639_2_code": "est"}
];
var user = [{name: "john",language: "eng",country: "US"}];
var language = languages.find(l => l.iso_639_2_code === user[0].language);
var languageName = language && language.name; // <-- also prevent error when there is no corresponding language found
console.log(languageName);
EDIT:
With multiple user, it will be:
var languages = [
{"name": "english", "iso_639_2_code": "eng"},
{"name": "esperanto","iso_639_2_code": "epo"},
{"name": "estonian","iso_639_2_code": "est"}
];
var users = [
{name: "john",language: "eng",country: "US"},
{name: "john",language: "epo",country: "Esperanto"}
];
var languageNames = languages.filter(
l => users.find(u => l.iso_639_2_code === u.language)
).map(lang => lang.name);
console.log(languageNames);
Use find
var output = languages.find(s => s.iso_639_2_code == user[0].language).name;
Demo
var languages = [{
"name": "english",
"iso_639_2_code": "eng"
},
{
"name": "esperanto",
"iso_639_2_code": "epo"
},
{
"name": "estonian",
"iso_639_2_code": "est"
}
];
var user = [{
name: "john",
language: "eng",
country: "US"
}
];
var output = languages.find(s => s.iso_639_2_code == user[0].language).name;
console.log(output);
Or, if there are multiple users, and you want to find language name for each of them, then use map
var output = user.map(t =>
languages.find(s =>
s.iso_639_2_code == t.language).name);
Demo
var languages = [{
"name": "english",
"iso_639_2_code": "eng"
},
{
"name": "esperanto",
"iso_639_2_code": "epo"
},
{
"name": "estonian",
"iso_639_2_code": "est"
}
];
var user = [{
name: "john",
language: "eng",
country: "US"
}
];
var output = user.map(t =>
languages.find(s =>
s.iso_639_2_code == t.language).name);
console.log(output);
I think here is what you need , for output just run the snippet :
var languages = [
{
"name": "english",
"iso_639_2_code": "eng"
},
{
"name": "esperanto",
"iso_639_2_code": "epo"
},
{
"name": "estonian",
"iso_639_2_code": "est"
}
];
var user = [
{
name: "john",
language: "eng",
country: "US"
}
];
user.map(u => {
let flang = languages.filter(lang => lang.iso_639_2_code === u.language);
if(flang) {
u.language = flang[0].name;
}
return u;
})
console.log(user);
var languages=[
{"name":"english","iso_639_2_code":"eng"},
{"name":"esperanto","iso_639_2_code":"epo"},
{"name":"estonian","iso_639_2_code":"est"}
];
var user=[
{name:"john",language:"eng",country:"US"}
];
var languageFound = languages.find(lang => lang.iso_639_2_code === user[0].language);
if(languageFound){
var languageName = languageFound.name;
console.log(languageName);
}

AngularJS Array Comparison

I have got the following array of Usernames
Usernames = [
{
"id": 1,
"userName": "Jack",
"description": "jack is a nice guy",
"userRoleIds": [
1
]
},
{
"id": 2,
"userName": "Caroline",
"description": "Good girl",
"userRoleIds": [
2,3
]
},
{
"id": 3,
"userName": "Smith",
"description": "Smithyyyy",
"userRoleIds": [
1,2
]
}
]
And an array of userRoles.
userRoles = [
{
id: 1,
roleName: "Admin"
},
{
id: 2,
roleName: "Tester"
},
{
id: 3,
roleName: "Developer"
}
]
What i want to get done is first concat the arrays in in Usernames and userRoles to get the following result.
Usernames = [
{
"id": 1,
"userName": "Jack",
"description": "jack is a nice guy",
"userRoleIds": [
{
"id": 1,
"roleName" : "Admin"
}
]
},
{
"id": 2,
"userName": "Caroline",
"description": "Good girl",
"userRoleIds": [
{
"id": 2,
"roleName" : "Tester"
},
{
"id": 3,
"roleName" : "Developer"
}
]
},...
The second thing i want is to be able to filter for the roleName and userName seperated by pipe signs. As in type something in a text box that searches for userName and roleName for example.
if i type
Caroline, Tester
The result will be
result = [
{
"id": 2,
"userName": "Caroline",
"description": "Good girl",
"userRoleIds": [
2,3
]
},
{
"id": 3,
"userName": "Smith",
"description": "Smithyyyy",
"userRoleIds": [
1,2
]
}
]
What is the best practice for achieving this?
Thanks
Here is how I would do it. I prefer using services and take advantage of their functions to keep code clean.
app.service('UserService', function (PermisionsServices) {
var self = {
'list': [],
'load': function (Users) {//Pass your array of Users
angular.forEach(Users, function (user) {
angular.forEach(user.userRoleIds, function (role) {
self.user.userRolesIds.push(PermisionsServices.get(role));
});
self.list.push(user);
});
}, 'get': function (id) {
for (var i = 0; i < self.list.length; i++) {
var obj = self.list[i];
if (obj.id == id) {
return obj;
}
}
}
};
return self;
});
app.service('PermisionsServices', function () {
var self = {
'list': [],
'load': function (permisions) {//Pass your array of permisions
angular.forEach(permisions, function (permision) {
self.list.push(permision);
});
}, 'get': function (id) {
for (var i = 0; i < self.list.length; i++) {
var obj = self.list[i];
if (obj.id == id) {
return obj;
}
}
}
};
return self;
});
Afterwards, you can use it on your controller:
$scope.users=UserService;
And access each of the users as a separate object which can have multiple object permisions.
NOTE: Building the service (populating it) will of course depend on your app logic and controller, you could just easily remove the "load" function and just hardcode the list object by copy and pasting your arrays.
This is the approach I use to load data from API via resource.
Regards
Edit:
For use on the UI, you would just call:
<div ng-repeat='user in users.list'>
{{user.name}} has {{user.permissions}}
</div>
as the object information is already contained within it.
Edit 2:
If you want to search your data, then you can just add a filter like this:
<div ng-repeat='user in users.list | filter: filterList'>
{{user.name}} has {{user.permissions}}
</div>
And then on the controller:
$scope.filterList = function (user) {
if ($scope.filterTextBox) {
return user.name.indexOf($scope.filterTextBox) == 0;
}
return true;
}
Hope this works for you
I would do with pure JS like this. It won't take more than a single assignment line each.
var Usernames = [
{
"id": 1,
"userName": "Jack",
"description": "jack is a nice guy",
"userRoleIds": [
1
]
},
{
"id": 2,
"userName": "Caroline",
"description": "Good girl",
"userRoleIds": [
2,3
]
},
{
"id": 3,
"userName": "Smith",
"description": "Smithyyyy",
"userRoleIds": [
1,2
]
}
],
userRoles = [
{
id: 1,
roleName: "Admin"
},
{
id: 2,
roleName: "Tester"
},
{
id: 3,
roleName: "Developer"
}
],
modified = Usernames.reduce((p,c) => (c.userRoleIds = c.userRoleIds.map(e => e = userRoles.find(f => f.id == e)),p.concat(c)),[]),
query = ["Caroline","Tester"],
filtered = modified.filter(f => query.includes(f.userName) || f.userRoleIds.some(e => query.includes(e.roleName)));
console.log(JSON.stringify(modified,null,2));
console.log(JSON.stringify(filtered,null,2));
You can use lodash to achieve this.
var role = _.find(userRoles, function(role) {
return role.roleName == 'Tester';
});
_.find(Usernames, function(user) {
return user.userName == 'Caroline' || _.indexOf(user.userRoleIds, role.id)>=0;
});

Categories

Resources