I'm making an API request and running the data through groupBy and sortBy to get a slightly more structured object. Then I save it to the state.
useEffect(() => {
const fetchTeamData = async () => {
const result = await axios(
`https://example.com/api-call`,
).then((result) => {
let teams = _.groupBy(_.sortBy(result.data.results, "season"), "player_profile.team");
setTeamInfo(teams);
});
};
fetchTeamAffinityData();
}, []);
This gives me an object similar to:
{
Angels: [
0: {
player_profile:{
name: "John Doe",
number: "10"
},
season: 1
},
1: {
player_profile:{
name: "Mike Trout",
number: "21"
},
season: 2
}
],
Diamondbacks: [
0: {
player_profile:{
name: "Randy Johnson",
number: "51"
},
season: 1
},
1: {
player_profile:{
name: "Brandon Webb",
number: "16"
},
season: 2
}
],
}
This is the output I'm looking for:
Team: Angels
Player 1: John Doe
Player 1 Number: 10
Player 2: Mike Trout
Player 2 Number: 21
Team: Diamondbacks
Player 1: Randy Johnson
Player 1 Number: 51
Player 2: Bradon Webb
Player 2 Number: 17
Here is what I've tried, which gives me a loop of the team names. But since I'm mapping the object keys, I lose the actual data for the nested object.
{Object.keys(teamAffinityInfo?? "").map((team, index) => (
Team: {team}
))}
How can I work with deeply nested objects in a sensible way?
Based on your approach, you could store Object.keys() and then iterate on those keys to implement your logic.
JS
const teams = Object.keys(object1)
teams.forEach(team => {
const playerProfiles = Object1[team]
playerProfiles.forEach(p => {
// Your logic here
})
})
Better approach would be to use Object.entries() as mentioned by Ogod in the comment.
JS
const object1 = {
Angels: [
{
player_profile:{
name: "John Doe",
number: "10"
},
season: 1
},
{
player_profile:{
name: "Mike Trout",
number: "21"
},
season: 2
}
],
Diamondbacks: [
{
player_profile:{
name: "Randy Johnson",
number: "51"
},
season: 1
},
{
player_profile:{
name: "Brandon Webb",
number: "16"
},
season: 2
}
],
}
for (const [key, value] of Object.entries(object1)) {
const players = value
console.log("Team", key)
players.forEach((p,i) => {
console.log("Player",i, ":", p.player_profile.name)
console.log("Player",i, "Number :", p.player_profile.number)
})
}
Note: I slightly changed the structure of object, by removing the index in the profiles array.
Here is an iterative solution using object-scan
.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan#18.1.2/lib/index.min.js';
const myData = { Angels: [ { player_profile: { name: "John Doe", number: "10" }, season: 1 }, { player_profile: { name: "Mike Trout", number: "21" }, season: 2 } ], Diamondbacks: [ { player_profile: { name: "Randy Johnson", number: "51" }, season: 1 }, { player_profile: { name: "Brandon Webb", number: "16" }, season: 2 } ], };
const extract = (data) => {
const logic = {
'*': ({ key }) => `Team: ${key[0]}`,
'*[*].player_profile.name': ({ key, value }) => `Player ${key[1] + 1}: ${value}`,
'*[*].player_profile.number': ({ key, value }) => `Player ${key[1] + 1} Number: ${value}`
}
return objectScan(Object.keys(logic), {
breakFn: ({ context, matchedBy, key, value }) => {
context.push(...matchedBy.map((m) => logic[m]({ key, value })))
},
reverse: false
})(data, []);
}
console.log(extract(myData).join('\n'));
/* =>
Team: Angels
Player 1: John Doe
Player 1 Number: 10
Player 2: Mike Trout
Player 2 Number: 21
Team: Diamondbacks
Player 1: Randy Johnson
Player 1 Number: 51
Player 2: Brandon Webb
Player 2 Number: 16
*/
</script>
Disclaimer: I'm the author of object-scan
Related
I have an object that looks like the following:
const test = {
leagues: [
{
timezone: "GMT",
date: "1/2/2",
premierLeague: [
{ name: "Liverpool", age: 1892 },
{ name: "Manchester Utd", age: 1878 }
],
laLiga: [
{
team: "Real Madrid",
stadium: "Bernabeu"
},
{
team: "Barcelona",
stadium: "Camp Nou"
}
]
}
]
};
and I want the result to look like
const result = [
{ name: "Liverpool", age: 1892 },
{ name: "Manchester Utd", age: 1878 },
{
team: "Real Madrid",
stadium: "Bernabeu"
},
{
team: "Barcelona",
stadium: "Camp Nou"
}
];
I have tried to use flat() but am having trouble getting the arrays within the leagues. The result will be dynamic so I need to be able to get all the arrays within leagues. Can anyone point me in the correct direction to do this?
If your object structure doesn't go an deeper than that, this long one-liner should work:
const result = test.leagues.reduce((arr, obj) => Object.values(val).reduce((innerArr, val) => Array.isArray(val) ? innerArr.concat(val) : innerArr, arr), []);
Somewhat ungolfed:
const result = test.leagues.reduce((arr, obj) => {
return Object.values(val).reduce((innerArr, val) => {
return Array.isArray(val)
? innerArr.concat(val)
: innerArr
}, arr);
}), []);
You might be looking for
const result = test.leagues.flatMap(league =>
Object.values(league).filter(Array.isArray).flat()
);
This sounds weird, you'll end up with objects of different shapes in the same array. I'm not sure how you'll deal with that.
It looks like you're trying to concatenate every value of test.leagues that is itself an array.
const test = {
leagues: [{
timezone: "GMT",
date: "1/2/2",
premierLeague: [{
name: "Liverpool",
age: 1892
},
{
name: "Manchester Utd",
age: 1878
}
],
laLiga: [{
team: "Real Madrid",
stadium: "Bernabeu"
},
{
team: "Barcelona",
stadium: "Camp Nou"
}
]
}]
};
const output = [];
for (const league of test.leagues) {
for (const key in league) {
if (Array.isArray(league[key])) {
// Push each element in `league[key]` onto `output`
// so we don't have to flatten it later
output.push(...league[key]);
}
}
}
console.log({
output
});
Well, I'm also adding my 2 cents over here. I do agree with everyone else. See if this works:
const test = {
leagues: [
{
timezone: "GMT",
date: "1/2/2",
premierLeague: [
{ name: "Liverpool", age: 1892 },
{ name: "Manchester Utd", age: 1878 }
],
laLiga: [
{
team: "Real Madrid",
stadium: "Bernabeu"
},
{
team: "Barcelona",
stadium: "Camp Nou"
}
]
}
]
};
let finalArray = [];
function recursiveArr(obj, arrayToPush) {
for(const [key, val] of Object.entries(obj)) {
if(Array.isArray(obj[key])) {
arrayToPush.push(obj[key]);
continue;
}
const type = typeof obj[key];
if(type === "object") {
recursiveArr(obj[key], arrayToPush);
}
}
}
recursiveArr(test.leagues, finalArray);
console.log(finalArray.flat())
I have an array that looks like this (simplified):
[
{
name: 'Store1',
price: ['10'],
somethingElse: null
},
{
name: 'Store2',
price: ['5'],
somethingElse: 6
},
{
name: 'Store2',
price: ['15'],
somethingElse: 6
},
]
I need to turn it into this:
[
{
name: 'Store1',
price: ['10'],
somethingElse: null
},
{
name: 'Store2',
price: ['5', '15'],
somethingElse: 6
},
]
So for each object where the name is the same, it takes the price's for all of them and puts them in an array, and reduces amount of entries with the same name to one.
It needs to work for a much longer array, and work with many name's.
Let's assume that everything other than the price will be the same in each object that has the same name.
I have tried for too long using array.reduce but with no luck.
const input = [
{
name: 'Store1',
price: ['10'],
somethingElse: null
},
{
name: 'Store2',
price: ['5'],
somethingElse: 6
},
{
name: 'Store2',
price: ['15'],
somethingElse: 6
},
]
const output = input.reduce((a,c) => {
if(a.find(i => i.name === c.name)) { // Check if 'name' already exist
return a.map(el => el.name === c.name ? ({...el, price: [...el.price, ...c.price]}) : el) // updating price for existed name
} else {
return [...a, c] // no such name, should add to ouput result
}
}, [])
console.log(output)
You can use grouping by hash approach:
const data = [{"name":"Store1","price":["10"],"somethingElse":null},{"name":"Store2","price":["5"],"somethingElse":6},{"name":"Store2","price":["15"],"somethingElse":6}];
const result = Object.values(data.reduce((acc, obj) => {
acc[obj.name] ??= obj;
if (!acc[obj.name].price.includes(...obj.price))
acc[obj.name].price.push(...obj.price);
return acc;
}, {}));
console.log(result);
.as-console-wrapper{min-height: 100%!important; top: 0}
This question already has answers here:
Curly Brackets in Arrow Functions
(3 answers)
Closed 1 year ago.
I have two nested object arrays, one is an array describing a school (id, name, tests it conducts along with their counts), and another is an array of school teachers corresponding to the schools in the first array. Here is some sample data for both arrays:
import { ObjectId } from 'mongodb'
monthlySchoolTests = [{
schoolId: ObjectId('804d8527f390ghz26e3426j6'),
schoolName: "School1"
schoolTests: [
{ testName: "Chemistry", count: 15 },
{ testName: "Music", count: 8 }
]
},
{
schoolId: ObjectId('804ef074384b3d43f125ghs5'),
schoolName: "School2"
schoolTests: [
{ testName: "Math", count: 3 },
{ testName: "Physics", count: 12 },
{ testName: "Biology", count: 10 }
]
}
]
schoolTeachers = [{
schoolId: ObjectId('804ef074384b3d43f125ghs5')
schoolName: "School2"
teachers: [
{ name: "Michael", email: "michael#school2.edu" },
{ name: "Jane", count: "jane#school2.edu" },
{ name: "Lukas", count: "lukas#school2.edu" }
]
},
{
schoolId: ObjectId('804d8527f390ghz26e3426j6')
schoolName: "School1"
teachers: [
{ name: "Cleo", email: "cleo#school1.edu" },
{ name: "Celine", count: "celine#school1.edu" },
{ name: "Ike", count: "ike#school1.edu" }
]
}
]
I obtained the second array by passing as an argument an array of schoolIds that I got from the query results for the first array, so I know that both arrays have about the same number of objects (assuming queries don't return null values). I am trying to link the test information in the first array with the school teacher in the second array but the .find() method is not working for me. Here is my code:
monthlySchoolTests.map(testObj => {
const selectedSchoolTeachers = schoolTeachers.find(teacherObj => {
String(testObj.schoolId) === String(teacherObj.schoolId)
})
console.log(selectedSchoolTeachers)
})
For some reason, I only get undefined even though I have tested this by mapping through each object in the first array and asserting that there is a match for every schoolId in the second array. (console.log yields true).
Sorry for any errors as this is my first time posting. Would appreciate any help!
Well, you could just quick-generate a combined array that had everything you need in it:
let combined = monthlySchoolTests.map(e => ({...e, teachers: schoolTeachers.filter(t => t.schoolId === e.schoolId)[0].teachers}))
const ObjectId = (v) => v; // for testing
let monthlySchoolTests = [{
schoolId: ObjectId('804d8527f390ghz26e3426j6'),
schoolName: "School1",
schoolTests: [
{ testName: "Chemistry", count: 15 },
{ testName: "Music", count: 8 }
]
},
{
schoolId: ObjectId('804ef074384b3d43f125ghs5'),
schoolName: "School2",
schoolTests: [
{ testName: "Math", count: 3 },
{ testName: "Physics", count: 12 },
{ testName: "Biology", count: 10 }
]
}
]
let schoolTeachers = [{
schoolId: ObjectId('804ef074384b3d43f125ghs5'),
schoolName: "School2",
teachers: [
{ name: "Michael", email: "michael#school2.edu" },
{ name: "Jane", count: "jane#school2.edu" },
{ name: "Lukas", count: "lukas#school2.edu" }
]
},
{
schoolId: ObjectId('804d8527f390ghz26e3426j6'),
schoolName: "School1",
teachers: [
{ name: "Cleo", email: "cleo#school1.edu" },
{ name: "Celine", count: "celine#school1.edu" },
{ name: "Ike", count: "ike#school1.edu" }
]
}
]
let combined = monthlySchoolTests.map(e => ({...e, teachers: schoolTeachers.filter(t => t.schoolId === e.schoolId)[0].teachers}))
console.log(combined)
This question already has answers here:
How to merge multiple array of object by ID in javascript?
(5 answers)
Closed 1 year ago.
Input arrays
arr1=[{"CATALOG":"book1","ID":"1"},{"CATALOG":"book2","ID":"2"},{"CATALOG":"book3","ID":"3"},{"CATALOG":"book4","ID":"12"}]
arr2=[{"NAME":"TOM","ID":"1"},{"NAME":"STEVE","ID":"22"},{"NAME":"HARRY","ID":"2"},{"NAME":"TIM","ID":"3"},{"NAME":"DAVE","ID":"12"},{"NAME":"WIL","ID":"12"},{"NAME":"PETER","ID":"94"},{"NAME":"SAVANNAH","ID":"77"}]
Expected Output
[{"CATALOG":"book1","ID":"1","NAME":"TOM"},
{"CATALOG":"book2","ID":"2","NAME":"HARRY"},
{"CATALOG":"book3","ID":"3","NAME":"TIM"},
{"CATALOG":"book4","ID":"12","NAME":"WIL"}
expected output is that 2 arrays have to be combined based on id. If ID doesn't exist then that particular object is skipped
I tried using
[arr1,arr2].reduce((a, b) => a.map((c, i) => Object.assign({}, c, b[i])));
But not getting the desired output
You can use map and find
const arr1 = [
{ CATALOG: "book1", ID: "1" },
{ CATALOG: "book2", ID: "2" },
{ CATALOG: "book3", ID: "3" },
{ CATALOG: "book4", ID: "12" },
];
const arr2 = [
{ NAME: "TOM", ID: "1" },
{ NAME: "STEVE", ID: "22" },
{ NAME: "HARRY", ID: "2" },
{ NAME: "TIM", ID: "3" },
{ NAME: "DAVE", ID: "12" },
{ NAME: "WIL", ID: "12" },
{ NAME: "PETER", ID: "94" },
{ NAME: "SAVANNAH", ID: "77" },
];
const output = arr1.map(a => ({
...a,
NAME: arr2.find(x => x.ID === a.ID).NAME,
}));
console.log(output);
There may be cleverer solutions, but assuming arr2 always contains the corresponding ID, I'd do it simply with :
const arr1=[{"CATALOG":"book1","ID":"1"},{"CATALOG":"book2","ID":"2"},{"CATALOG":"book3","ID":"3"},{"CATALOG":"book4","ID":"12"}];
const arr2=[{"NAME":"TOM","ID":"1"},{"NAME":"STEVE","ID":"22"},{"NAME":"HARRY","ID":"2"},{"NAME":"TIM","ID":"3"},{"NAME":"DAVE","ID":"12"},{"NAME":"WIL","ID":"12"},{"NAME":"PETER","ID":"94"},{"NAME":"SAVANNAH","ID":"77"}];
const output = arr1.map(obj1 => {
const obj2 = arr2.find(o2 => o2.ID === obj1.ID);
return Object.assign(obj1, obj2)
});
console.log(output)
Or as a one-liner :
const output = arr1.map(obj1 => Object.assign(obj1, arr2.find(o2 => o2.ID === obj1.ID)))
I have an array containing several objects similar to the following:
{person: {name: "Steve", id: 1}, role: 1}
{person: {name: "Phil", id: 2}, role: 1}
{person: {name: "Steve", id: 1}, role: 3}
{person: {name: "Phil", id: 2}, role: 6}
My intention is to return an array of the same type, but I'd like to return only one object per "person" with their highest role.
I understand the following will give me a single object with the highest role.
array.reduce((prev, cur) => prev.role > cur.role ? prev : cur);
How do I return each unique person and their corresponding highest role as a new array?
Like so:
{person: {name: "Steve", id: 1}, role: 3}
{person: {name: "Phil", id: 2}, role: 6}
You need to collect the objects and if you have already one with the same id check the role and take the greater one.
var data = [{ person: { name: "Steve", id: 1 }, role: 1 }, { person: { name: "Phil", id: 2 }, role: 1 }, { person: { name: "Steve", id: 1 }, role: 3 }, { person: { name: "Phil", id: 2 }, role: 6 }],
grouped = data.reduce((r, o) => {
var index = r.findIndex(({ person: { id } }) => id === o.person.id);
if (index === -1) {
r.push(o);
return r;
}
if (r[index].role < o.role) {
r[index] = o;
}
return r;
}, []);
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Group the items by person.id with Array.reduce(), but for each id store only the object with the highest role. Convert back to array using Object.values():
const data = [{ person: { name: "Steve", id: 1 }, role: 1 }, { person: { name: "Phil", id: 2 }, role: 1 }, { person: { name: "Steve", id: 1 }, role: 3 }, { person: { name: "Phil", id: 2 }, role: 6 }];
const result = Object.values(data.reduce((r, o) => {
const id = o.person.id;
if(!r[id] || r[id].role < o.role) r[id] = o;
return r;
}, []));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use Array.prototype.reduce and build out an object literal like below. Use the person.name property value as the object key and then just update the role value if you find a higher value:
var people = [{
person: {
name: "Steve",
id: 1
},
role: 1
}, {
person: {
name: "Phil",
id: 2
},
role: 1
}, {
person: {
name: "Steve",
id: 1
},
role: 3
}, {
person: {
name: "Phil",
id: 2
},
role: 6
}];
var highRoleByPerson = people.reduce((accum, el) => {
if (accum[el.person.name]) {
if (el.role > accum[el.person.name].role) {
accum[el.person.name].role = el.role;
}
} else {
accum[el.person.name] = {
person: {
name: el.person.name,
id: el.person.id
},
role: 0
};
}
return accum;
}, {});
console.log(highRoleByPerson);