Group by array of objects by a nested key - javascript

I have the following data:
const data = [
{
parent: {
id: "1",
name: "Europe"
},
item: {
name: "Italy"
},
score: 5
},
{
parent: {
id: "1",
name: "Europe"
},
item: {
name: "France"
},
score: 4.5
},
{
parent: {
id: "1",
name: "Europe"
},
item: {
name: "UK"
},
score: 4.9
},
{
parent: {
id: "2",
name: "Afrique"
},
item: {
name: "Morocco"
},
score: 3.1
},
{
parent: {
id: "2",
name: "Afrique"
},
item: {
name: "Egypt"
},
score: 3.9
}
];
I want to group it based on the parent.id and calculate the average score, so I can have the following result:
[
{
parent: {
id: "1",
name: "Europe",
items: [
{
name: "Italy"
},
{
name: "France"
},
{
name: "UK"
}
],
score: 4.8
}
},
{
parent: {
id: "2",
name: "Afrique",
items: [
{
name: "Morocco"
},
{
name: "Egypt"
}
],
score: 3.5
}
}
]
I used the following function, but it doesn't work for the nested key, and also it's doesn't return the desired result schema.
let group = cars.reduce((r, a) => {
console.log("a", a);
console.log('r', r);
r[a.make] = [...r[a.parent.id] || [], a];
return r;
}, {});
console.log("group", group);

You can use _reduce() function: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
var result = data.reduce((res, data) => {
if(!res[data.parent.id]) {
data.item = [data.item];
res[data.parent.id] = data;
} else {
res[data.parent.id]['item'].push(data['item']);
res[data.parent.id]['score'] = (res[data.parent.id]['score'] + data['score'])/2;
}
return res;
}, [])
.filter(x => x != null)
const data = [
{
parent: {
id: "1",
name: "Europe"
},
item: {
name: "Italy"
},
score: 5
},
{
parent: {
id: "1",
name: "Europe"
},
item: {
name: "France"
},
score: 4.5
},
{
parent: {
id: "1",
name: "Europe"
},
item: {
name: "UK"
},
score: 4.9
},
{
parent: {
id: "2",
name: "Afrique"
},
item: {
name: "Morocco"
},
score: 3.1
},
{
parent: {
id: "2",
name: "Afrique"
},
item: {
name: "Egypt"
},
score: 3.9
}
];
var result = data.reduce((res, data) => {
if(!res[data.parent.id]) {
data.item = [data.item];
res[data.parent.id] = data;
} else {
res[data.parent.id]['item'].push(data['item']);
res[data.parent.id]['score'] = (res[data.parent.id]['score'] + data['score'])/2;
}
return res;
}, [])
.filter(x => x != null)
console.log(result)

Create an object/hashmap, then format the resulting object into an array.
let continents = {}
data.forEach(function(country){
const continent_id = country.parent.id
let continent = continents[continent_id]
if(!continent){
continent = {
id: continent_id,
name: country.parent.name,
items: [],
}
continents[continent_id] = continent
}
continent.items.push({
name: country.item.name,
score: country.score
})
})
continents = Object.entries(continents).map(item => ({parent: item[1]}))
console.log(continents)
Output:
[
{
"parent":{
"id":"1",
"name":"Europe",
"items":[
{
"name":"Italy",
"score":5
},
{
"name":"France",
"score":4.5
},
{
"name":"UK",
"score":4.9
}
]
}
},
{
"parent":{
"id":"2",
"name":"Afrique",
"items":[
{
"name":"Morocco",
"score":3.1
},
{
"name":"Egypt",
"score":3.9
}
]
}
}
]

From the data you've provided if you additionaly need to count average of score property, use the following reduce method: it will iterate trough your data, group it and calculate total score value and count of score values. And after reduce groups object perform map that will calculate average for score for all the groups using totalScore and scoreCount
const data = [
{
parent: {
id: "1",
name: "Europe"
},
item: {
name: "Italy"
},
score: 5
},
{
parent: {
id: "1",
name: "Europe"
},
item: {
name: "France"
},
score: 4.5
},
{
parent: {
id: "1",
name: "Europe"
},
item: {
name: "UK"
},
score: 4.9
},
{
parent: {
id: "2",
name: "Afrique"
},
item: {
name: "Morocco"
},
score: 3.1
},
{
parent: {
id: "2",
name: "Afrique"
},
item: {
name: "Egypt"
},
score: 3.9
}
];
let group = data.reduce((acc, rec) => {
if (acc.find(item => item.parent.id === rec.parent.id))
{
const idx = acc.findIndex(item => item.parent.id === rec.parent.id)
acc[idx].parent.items = acc[idx].parent.items.concat(rec.item)
acc[idx].parent.score += rec.score
acc[idx].parent.scoreCount +=1
} else {
acc = acc.concat({parent: {...rec.parent, score: rec.score, items: [rec.item], scoreCount:1}})
}
return acc
}, []).map(it => ({parent: {id: it.parent.id, name:it.parent.name, score: (it.parent.score / it.parent.scoreCount), items: it.parent.items}}));
console.log("group", group);

Related

Circular Referenced Objects Javascript

Having issues with this exercise:
// Let’s say we have an array of artists and we want to create a map-like object of their instruments.
const artists = [
{
id: '1',
name: 'Jimi Hendrix',
instrument: {
id: '1',
name: 'Guitar',
color: 'wood',
}
},
{
id: '2',
name: 'Jimmy Page',
instrument: {
id: '1',
name: 'Guitar',
color: 'wood',
}
},
{
id: '3',
name: 'Krist Novoselic',
instrument: {
id: '2',
name: 'Bass',
color: 'black',
}
},
{
id: '4',
name: 'Emmanuelle Proulx',
},
{
id: '5',
name: 'Jimmy Chamberlin',
instrument: {
id: '3',
name: 'Drums'
}
},
];
/* Expected results */
/* {
1: {
name: 'Guitar',
color: 'wood',
},
...
} */
const result = [];
artists.map((item) => {if ((item.instrument !== undefined)) {result.push(item.instrument.id = item.instrument)}});
So far I've extracted th instruments without undefined, but the ids are reference to ids and cannot get to extract the number id or to build it with the proper structure because of the circular reference.
So to use the map you'd still get undefined values. You probably would want to use reduce and do the following.
const artists = [
{
id: "1",
name: "Jimi Hendrix",
instrument: {
id: "1",
name: "Guitar",
color: "wood",
},
},
{
id: "2",
name: "Jimmy Page",
instrument: {
id: "1",
name: "Guitar",
color: "wood",
},
},
{
id: "3",
name: "Krist Novoselic",
instrument: {
id: "2",
name: "Bass",
color: "black",
},
},
{
id: "4",
name: "Emmanuelle Proulx",
},
{
id: "5",
name: "Jimmy Chamberlin",
instrument: {
id: "3",
name: "Drums",
},
},
];
const instruments = artists.reduce((acc, artist) => {
if (!artist.instrument) return acc;
const { id, name, color } = artist.instrument;
acc[id] = { name, color };
return acc;
}, {});
console.log(instruments);
try this
const instruments = artists.reduce((acc, artist) => {
if (!artist.instrument) return acc;
acc[artist.instrument.id] = {
name: artist.instrument.name,
color: artist.instrument.color
};
return acc;
}, {});
result
{
"1": {
"name": "Guitar",
"color": "wood"
},
"2": {
"name": "Bass",
"color": "black"
},
"3": {
"name": "Drums"
}
}

How to compare two array of objects and return the not matching object?

obj1 is the original object and obj2 is the changed object. I want to get the key , value pair and the type of all the changed object inside obje2 array of objects.
So, I need something like this where if "name" or "id" value is different in obj2 return the object along with the type.
changedObj = [
{
type:"mobile",
name:"Temple Runs",
id:2259
},
{
type:"pc",
name:"Pubgs",
id:222
}
]
obj1 = [
{
type: "mobile",
games: [
{
name: "Temple Run",
id: 2259,
},
{
name: "Subway Surfer",
id: 2271,
},
{
name: "Pubg",
id: 2272,
},
],
},
{
type: "pc",
games: [
{
name: "Pubg",
id: 222,
},
{
name: "Fortnite",
id: 2274,
},
{
name: "Nfs",
id: 2272,
},
],
},
];
obj2 = [
{
type: "mobile",
games: [
{
name: "Temple Runs",
id: 2259,
},
{
name: "Subway Surfer",
id: 2271,
},
{
name: "Pubg",
id: 2272,
},
],
},
{
type: "pc",
games: [
{
name: "Pubgs",
id: 222,
},
{
name: "Fortnite",
id: 2274,
},
{
name: "Nfs",
id: 2272,
},
],
},
];
How to achieve something like this ?
In order to find the difference, you will need to:
Map all of the updated platforms (type and games)
Filter the updated games and locate the original game by ID
Flat-map the games in each platform and include the type
const main = () => {
const delta = diff(changed, data);
console.log(delta);
};
const diff = (updated, original) =>
updated
.map(({ type, games }) => ({
type,
games: games
.filter(({ name, id }) => original
.find(platform => platform.type === type).games
.find(game => game.id === id)?.name !== name)
}))
.flatMap(({ type, games }) =>
games.map(({ name, id }) =>
({ name, id, type })));
const data = [{
type: "mobile",
games: [
{ name: "Temple Run", id: 2259 },
{ name: "Subway Surfer", id: 2271 },
{ name: "Pubg", id: 2272 }
],
}, {
type: "pc",
games: [
{ name: "Pubg", id: 222 },
{ name: "Fortnite", id: 2274 },
{ name: "Nfs", id: 2272 }
]
}];
const changed = [{
type: "mobile",
games: [
{ name: "Temple Runs", id: 2259 },
{ name: "Subway Surfer", id: 2271 },
{ name: "Pubg", id: 2272 }
],
}, {
type: "pc",
games: [
{ name: "Pubgs", id: 222 },
{ name: "Fortnite", id: 2274 },
{ name: "Nfs", id: 2272 }
]
}];
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }

JavaScript - transform object, consolidate

I'm trying to transform the object below. I need to create a new array of unique locations, with the location and item objects in each node.
With the help of JackOfAshes I was able to get halfway there in this PEN
Transform this:
const orig = [
{
item: {
name: "cat",
id: "ca_123"
},
location: {
name: "porch",
id: "por_123"
}
},
{
item: {
name: "dog",
id: "do_123"
},
location: {
name: "porch",
id: "por_123"
}
},
{
item: {
name: "snake",
id: "sn_123"
},
location: {
name: "forest",
id: "for_123"
}
},
{
item: {
name: "bird",
id: "bi_123"
},
location: {
name: "forest",
id: "for_123"
}
},
{
item: {
name: "beer",
id: "be_123"
},
location: {
name: "fridge",
id: "fri_123"
}
}
];
Into this:
const desired = [
{
name: "porch",
id: "por_123",
items: [
{
name: "cat",
id: "ca_123"
},
{
name: "dog",
id: "do_123"
}
]
},
{
name: "forest",
id: "for_123",
items: [
{
name: "snake",
id: "sn_123"
},
{
name: "bird",
id: "bi_123"
}
]
},
{
name: "fridge",
id: "fri_123",
items: [
{
name: "beer",
id: "be_123"
}
]
}
];
You can do it, or use reduce
const orig = [
{
item: {
name: "cat",
id: "ca_123"
},
location: {
name: "porch",
id: "por_123"
}
},
{
item: {
name: "dog",
id: "do_123"
},
location: {
name: "porch",
id: "por_123"
}
},
{
item: {
name: "snake",
id: "sn_123"
},
location: {
name: "forest",
id: "for_123"
}
},
{
item: {
name: "bird",
id: "bi_123"
},
location: {
name: "forest",
id: "for_123"
}
},
{
item: {
name: "beer",
id: "be_123"
},
location: {
name: "fridge",
id: "fri_123"
}
}
];
let formattedData = {}
orig.forEach(data=>{
if(!formattedData[data.location.id]) formattedData[data.location.id]= {
id: data.location.id,
name: data.location.name,
items:[]
}
formattedData[data.location.id].items.push(data.item)
})
const finalResponse = Object.entries(formattedData).map((e) => ( { ...e[1] } ));
console.log(finalResponse)

How to insert entries from into a nested JavaScript Object

I'm facing trouble in making a nested insertion of a particular entry into my data structure. The 'positionValue' in the 'data' object below has to be inserted into 'mystructure' based on whether it is Team1 or Team2, and based on the category 'Lombard Loans/Time Deposits/Demand Deposits' and then further based on 'name' of the product (the last nested structure).
The original object:
data: [
{
balanceSheetPositionResults: [
{
positionValue: 12,
balanceSheetPosition: {
name: "asset_bc_lombard_a_onsight",
category: "LOMBARD_LOANS",
type: "ASSET"
},
},
{
positionValue: 58,
balanceSheetPosition: {
name: "liability_bc_timedeposits",
category: "TIME_DEPOSITS",
type: "ASSET"
},
},
{
positionValue: 58,
balanceSheetPosition: {
name: "liability_bc_demanddeposits",
category: "DEMAND_DEPOSITS",
type: "ASSET"
},
},
{
positionValue: 10,
balanceSheetPosition: {
name: "asset_bc_lombard_a_lt1m",
category: "LOMBARD_LOANS",
type: "ASSET"
},
}
],
bank: {
name: "Team1"
},
game: {
name: "TestGame"
},
bsSum: 2,
period: {
index: 1
},
},
{
balanceSheetPositionResults: [
{
positionValue: 12,
balanceSheetPosition: {
name: "asset_bc_lombard_a_onsight",
category: "LOMBARD_LOANS",
type: "ASSET"
},
},
{
positionValue: 58,
balanceSheetPosition: {
name: "liability_bc_timedeposits",
category: "TIME_DEPOSITS",
type: "ASSET"
},
},
{
positionValue: 58,
balanceSheetPosition: {
name: "liability_bc_demanddeposits",
category: "DEMAND_DEPOSITS",
type: "ASSET"
},
},
{
positionValue: 10,
balanceSheetPosition: {
name: "asset_bc_lombard_a_lt1m",
category: "LOMBARD_LOANS",
type: "ASSET"
},
}
],
bank: {
name: "Team2"
},
game: {
name: "TestGame"
},
bsSum: 2,
period: {
index: 1
}
}
]
The structure that I made after some transformation (this is just a snippet):
{ mystructure:
[
{ name: 'Team2',
LOMBARD_LOANS:
[ { name: 'asset_bc_lombard_a_onsight'
},
{ name: 'asset_bc_lombard_a_lt1m'
}
],
DEMAND_DEPOSITS:
[ { name: 'liability_bc_demanddeposits'
}
],
TIME_DEPOSITS:
[ { name: 'liability_bc_timedeposits'
}
]
},
{ name: 'Team1',
LOMBARD_LOANS:
[ { name: 'asset_bc_lombard_a_onsight'
},
{ name: 'asset_bc_lombard_a_lt1m'
}
],
DEMAND_DEPOSITS:
[ { name: 'liability_bc_demanddeposits'
}
],
TIME_DEPOSITS:
[ { name: 'liability_bc_timedeposits'
}
]
}
]
}
The result that would look like:
{ mystructure:
[
{ name: 'Team2',
LOMBARD_LOANS:
[ { name: 'asset_bc_lombard_a_onsight',
positionValue: 12
},
{ name: 'asset_bc_lombard_a_lt1m',
positionValue: 58
}
],
DEMAND_DEPOSITS:
[ { name: 'liability_bc_demanddeposits',
positionValue: 58
}
],
TIME_DEPOSITS:
[ { name: 'liability_bc_timedeposits',
positionValue: 10
}
]
},
{ name: 'Team1',
LOMBARD_LOANS:
[ { name: 'asset_bc_lombard_a_onsight',
positionValue: 12
},
{ name: 'asset_bc_lombard_a_lt1m',
positionValue: 58
}
],
DEMAND_DEPOSITS:
[ { name: 'liability_bc_demanddeposits',
positionValue: 58
}
],
TIME_DEPOSITS:
[ { name: 'liability_bc_timedeposits',
positionValue: 10
}
]
}
]
}
Assuming each bank name comes only once, pass your original array to this transformer :
function transformData(data) {
return data.map(entry => {
const loanType = {};
entry.balanceSheetPositionResults.forEach(balanceEntry => {
const { name, category, type } = balanceEntry.balanceSheetPosition;
if (!(category in loanType)) {
loanType[category] = [];
}
loanType[category].push({
name,
positionValue: balanceEntry.positionValue
});
});
return {
name: entry.bank.name,
...loanType
};
});
}

JavaScript ES6 better way to transform complex object

I am trying to transform complex JavaScript object. Below is my code. As you can see, it's a lot of code. I am looking for a better/common way to achieve the same result. Maybe ES6 map/Reduce? (I am not allow to do import/require)
function test() {
var input = {
number: 555,
obj1: {
fld1: "11",
persons: [
{
name: "smith",
phone: "222-222-2222"
}
],
},
obj2: {
obj3: {
day: "2019-02-04"
}
},
myArr: [
{
number: 444,
qty: 20,
unit: "ton",
item: {
item_id: 1,
description: "item 1"
}
},
{
number: 111,
qty: 15,
unit: "pieces",
item: {
item_id: 2,
description: "item 2"
}
}
]
}
var result = {
id: input.number,
object1: {
id: input.obj1.number,
contacts: getArr2(input)
},
object2: {
date: input.obj2.obj3.day,
},
list: getArr1(input),
}
return result; // echo back the input received
}
console.log(JSON.stringify(test()));
function getArr1(input) {
var arr = [];
input.myArr.forEach(function (prod) {
let p = {
id: prod.number,
itemId: prod.item.item_id,
description: prod.item.description,
quantity: {
value: prod.qty,
uom: prod.unit
}
}
arr.push(p);
});
return arr;
}
function getArr2(input) {
var arr = [];
input.obj1.persons.forEach(function (person) {
let p = {
name: person.name,
cell: person.phone
}
arr.push(p);
});
return arr;
}
And the result is
{
"id": 555,
"object1": {
"contacts": [{
"name": "smith",
"cell": "222-222-2222"
}]
},
"object2": {
"date": "2019-02-04"
},
"list": [{
"id": 444,
"itemId": 1,
"description": "item 1",
"quantity": {
"value": 20,
"uom": "ton"
}
}, {
"id": 111,
"itemId": 2,
"description": "item 2",
"quantity": {
"value": 15,
"uom": "pieces"
}
}]
}
You could use the power of destructuring and renaming.
function getProds(products) {
return products.map(({ number: id, qty: value, unit: uom, item: { item_id: itemId, description } }) =>
({ id, itemId, description, quantity: { value, uom } }));
}
function getPersons(persons) {
return persons.map(({ name, phone: cell }) => ({ name, cell }));
}
function convert({ number: id, obj1, obj2: { obj3: { day: date } }, myArr }) {
return {
id,
object1: {
id: obj1.number,
contacts: getPersons(obj1.persons)
},
object2: { date },
list: getProds(myArr)
};
}
var data = { number: 555, obj1: { fld1: "11", persons: [{ name: "smith", phone: "222-222-2222" }], }, obj2: { obj3: { day: "2019-02-04" } }, myArr: [{ number: 444, qty: 20, unit: "ton", item: { item_id: 1, description: "item 1" } }, { number: 111, qty: 15, unit: "pieces", item: { item_id: 2, description: "item 2" } }] };
console.log(convert(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You're on the right track with map/reduce.
Here's an example (getArr1 would be similar):
function getArr2(input) {
// Don't need map if new object is identical
// Could also do the mapping within the reduce callback
return input.obj1.persons
.map(person => ({ name: person.name, cell: person.phone }))
.reduce((accumulator, currentValue) => {
accumulator.push(currentValue);
return accumulator;
}, []);
}
There's another example in the documentation at: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#Remove_duplicate_items_in_array

Categories