Extracting objects out of arrays - javascript

I want to map from this:
companies: {
apples: {
Q7: {
price: 1,
},
Q6: {
price: 1,
},
peaches: {
Q7: {
price: 1,
},
Q6: {
price: 1,
},
},
};
to this:
{ "companies": {
"apples": [
{
"name": "Q7",
"price": 1
},{
"name": "Q6",
"price": 1
}
],
"peaches": [
{
"name": "Q7",
"price": 1
},{
"name": "Q6",
"price": 1
}
]
}
}
How I am trying to achieve this:
I have a selector which gives me the companies object and then I map over it and assemble my object but I don't get it quite right.
This is my function:
const weaponProducts = Object.entries(getCompanies(state)).map(([companyType, companies]) => {
const prod = Object.entries(companies).map(([key, companies]) => {
return {
name: key,
price: companies.price
}
});
return {
[companyType]: prod
};
});
getCompanies(state) returns the following object:
{
"companies": {
"apples": {
"Q7": {
"price": 1
},
"Q6": {
"price": 1
}
},
"peaches": {
"Q7": {
"price": 1
},
"Q6": {
"price": 1
}
}
}
}
The result of the function is the following. But as explained I want it to look like the second code section of my post.
[
{
"apples": [
{
"name": "Q7",
"price": 1
},
{
"name": "Q6",
"price": 1
},
]
},
{
"peaches": [
{
"name": "Q7",
"price": 1
},
{
"name": "Q6",
"price": 1
},
]
}
]

since your desired output is an object, not an array, you should use reduce instead of map:
let companies = {
apples: {
Q7: {
price: 1,
},
Q6: {
price: 1,
},
},
peaches: {
Q7: {
price: 1,
},
Q6: {
price: 1,
},
},
}
let fruit = Object.keys(companies)
let output = fruit.reduce((output, currentFruit) => {
output[currentFruit] = Object.keys(companies[currentFruit]).map(q => {
return { name: q, price: companies[currentFruit][q].price }
})
return output
}, {});
console.log(output);
(I think there was a syntax error in your companies object, I corrected in the snippet)

You can also take entries and then map the objects accordingly.
var companies = { apples: { Q7: { price: 1, }, Q6: { price: 1, }, }, peaches: { Q7: { price: 1, }, Q6: { price: 1, } }};
const result = (inputObj) =>
Object.fromEntries(
Object.entries(inputObj).map(([key, obj]) => [
key,
Object.entries(obj).map(([name, val]) => ({ name, ...val })),
])
);
console.log(result(companies));

If you're down to try something new, here's a different way to express the desired transformation using a library I authored.
const x = { companies: { apples: { Q7: { price: 1, }, Q6: { price: 1, }, }, peaches: { Q7: { price: 1, }, Q6: { price: 1, }, }, } }
const { pipe, fork, map, tap, get } = rubico
const y = map(map(pipe([
Object.entries,
map(fork({
name: get(0),
price: get([1, 'price']),
})),
])))(x)
console.log(JSON.stringify(y, null, 2))
<script src="https://unpkg.com/rubico/index.js"></script>

Related

Consolidate object value containing array : Javascript

I am having array of objects that look like this:
const test = {
cat1: {
id: "c1",
name: "category1",
items: [
{
itemName: "item1",
points: 1,
used: true
},
{
itemName: "item2",
points: 3,
used: false
},
{
itemName: "item3",
points: 5,
used: true
}
]
},
cat2: {
id: "c2",
name: "category2",
items: [
{
itemName: "item4",
points: 7,
used: true
},
{
itemName: "item5",
points: 9,
used: false
}
]
},
cat3: {
id: "c3",
name: "category3",
items: [
{
itemName: "item6"
}
]
}
};
I want the output to be an object something like this
{ available: 12 , used: 13}
Need to go over the items array, and derive the available and used based on if used boolean under each object. If its true, add it with used else add it with available entry.Some object may not have points and used , those need to be ignored.
Can someone help with the approach
Code that I tried
const result = Object.values(test).reduce(
(acc, obj) => {
for (let i = 0; i < obj.items.length; i++) {
if (obj.items[i].used) {
return (acc.used = acc.used + obj.items[i].used);
} else acc.available = acc.available + obj.items[i].available;
}
},
{ available: 0, used: 0 }
);
You could flat the arrays and add with the help of an array.
const
test = { cat1: { id: "c1", name: "category1", items: [{ itemName: "item1", points: 1, used: true }, { itemName: "item2", points: 3, used: false }, { itemName: "item3", points: 5, used: true }] }, cat2: { id: "c2", name: "category2", items: [{ itemName: "item4", points: 7, used: true }, { itemName: "item5", points: 9, used: false }] }, cat3: { id: "c3", name: "category3", items: [{ itemName: "item6" }] } },
result = Object
.values(test)
.flatMap(({ items }) => items)
.reduce((r, { points, used }) => {
if (points) r[['available', 'used'][+used]] += points;
return r;
}, { available: 0 , used: 0 });
console.log(result);
2nd request.
const
test = { cat1: { id: "c1", name: "category1", items: [{ itemName: "item1", points: 1, used: true }, { itemName: "item2", points: 3, used: false }, { itemName: "item3", points: 5, used: true }] }, cat2: { id: "c2", name: "category2", items: [{ itemName: "item4", points: 7, used: true }, { itemName: "item5", points: 9, used: false }] }, cat3: { id: "c3", name: "category3", items: [{ itemName: "item6" }] } },
result = Object
.values(test)
.flatMap(({ items }) => items)
.reduce((r, { points = 0, used = false }) => {
r.used += used && points;
r.available += points;
return r;
}, { available: 0 , used: 0 });
console.log(result);
You're returning on the first iteration of the loop. You need to increment acc.used or acc.available in the loop, but only return acc at the end of the loop.
You need to change the parameter of the callback function to curr to match how you use it inside the function.
obj.items[i].used is not a number to add. You should just increment the counter. And there's no obj.items[i].available.
const test = {
cat1: {
id: "c1",
name: "category1",
items: [{
itemName: "item1",
points: 1,
used: true
},
{
itemName: "item2",
points: 3,
used: false
},
{
itemName: "item3",
points: 5,
used: true
}
]
},
cat2: {
id: "c2",
name: "category2",
items: [{
itemName: "item4",
points: 7,
used: true
},
{
itemName: "item5",
points: 9,
used: false
}
]
},
cat3: {
id: "c3",
name: "category3",
items: [{
itemName: "item6"
}]
}
};
const result = Object.values(test).reduce(
(curr, obj) => {
obj.items.forEach(item => {
if (item.used) {
curr.used++;
} else {
curr.available++;
}
});
return curr;
}, {
available: 0,
used: 0
});
console.log(result);
Do first a map() and .flat(), than the .reduce():
const input = { cat1: { id: "c1", name: "category1", items: [ { itemName: "item1", points: 1, used: true }, { itemName: "item2", points: 3, used: false }, { itemName: "item3", points: 5, used: true } ] }, cat2: { id: "c2", name: "category2", items: [ { itemName: "item4", points: 7, used: true }, { itemName: "item5", points: 9, used: false } ] }, cat3: { id: "c3", name: "category3", items: [ { itemName: "item6" } ] } };
const result = Object.values(input).map(obj => obj.items).flat().reduce((acc, obj) => {
if(obj.used) {
acc.used += obj.points;
} else {
acc.available += obj.points || 0;
}
return acc;
}, {available: 0, used: 0})
console.log(result);
Output:
{
"available": 12,
"used": 13
}
UPDATE 1 to get overall points for available:
const input = { cat1: { id: "c1", name: "category1", items: [ { itemName: "item1", points: 1, used: true }, { itemName: "item2", points: 3, used: false }, { itemName: "item3", points: 5, used: true } ] }, cat2: { id: "c2", name: "category2", items: [ { itemName: "item4", points: 7, used: true }, { itemName: "item5", points: 9, used: false } ] }, cat3: { id: "c3", name: "category3", items: [ { itemName: "item6" } ] } };
const result = Object.values(input).map(obj => obj.items).flat().reduce((acc, obj) => {
acc.available += obj.points || 0;
if(obj.used) {
acc.used += obj.points;
}
return acc;
}, {available: 0, used: 0})
console.log(result);
Output:
{
"available": 25,
"used": 13
}

How to group some properties of objects based on other property

I have a json response from the database like below:
"gradings": [
{
"gradingId": 1,
"enrollment": {
"enrollmentId": 1,
"rollNo": "1PC-1"
},
"grade": {
"name": "B-"
}
},
{
"gradingId": 2,
"enrollment": {
"enrollmentId": 1,
"rollNo": "1PC-1"
},
"grade": {
"name": "A+"
}
},
{
"gradingId": 3,
"enrollment": {
"enrollmentId": 2,
"rollNo": "1PC-2"
},
"grade": {
"name": "C-"
}
},
{
"gradingId": 4,
"enrollment": {
"enrollmentId": 2,
"rollNo": "1PC-2"
},
"grade": {
"name": "C-"
}
}
]
What I need is to transform above object into below structure:
I want to group grades in an array for the same enrollmentId.
"gradings": [
{
enrollmentId: 1,
rollNo: "1PC-1",
grades: [B-, A+]
},
{
enrollmentId: 2,
rollNo: "1PC-2",
grades: [C-, C-]
}
]
I didn't see any proper solution to get my requirements and I searched for lodash also.
What should I do? How can I achieve this? Thanks.
You should group grades by enrollmentId, have a array storing unique enrollments, map through that array and then complement the grades of that enrollment
Below solution (in vanilla js) could help you
const { gradings } = {
gradings: [
{
gradingId: 1,
enrollment: {
enrollmentId: 1,
rollNo: "1PC-1",
},
grade: {
name: "B-",
},
},
{
gradingId: 2,
enrollment: {
enrollmentId: 1,
rollNo: "1PC-1",
},
grade: {
name: "A+",
},
},
{
gradingId: 3,
enrollment: {
enrollmentId: 2,
rollNo: "1PC-2",
},
grade: {
name: "C-",
},
},
{
gradingId: 4,
enrollment: {
enrollmentId: 2,
rollNo: "1PC-2",
},
grade: {
name: "C-",
},
},
],
}
const enrollmentById = {}
const gradesByEnrollmentId = {}
gradings.forEach((grading) => {
if (!gradesByEnrollmentId[grading.enrollment.enrollmentId]) {
gradesByEnrollmentId[grading.enrollment.enrollmentId] = [grading.grade.name]
} else {
gradesByEnrollmentId[grading.enrollment.enrollmentId].push(grading.grade.name)
}
if (!enrollmentById[grading.enrollment.enrollmentId]) {
enrollmentById[grading.enrollment.enrollmentId] = { ...grading.enrollment }
}
})
const res = {
gradings: Object.values(enrollmentById).map((enrollment) => ({
...enrollment,
grades: gradesByEnrollmentId[enrollment.enrollmentId],
})),
}
console.log(res)

Group by array of objects by a nested key

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

Check if an element is common between 2 arrays and then assign values from it

In my angular 8 application, I have 2 arrays:
array1 = [{
"SubType": "2|3|4|5|6",
},
{
"SubType": "2",
},
{
"SubType": "3|4",
},
{
"SubType": "6",
},
{
"SubType": "3|6",
},
]
&
array2 = [{
"id": 2,
"type": "1",
},
{
"id": 3,
"type": "5",
},
{
"id": 4,
"type": "4",
},
{
"id": 5,
"type": "3",
},
{
"id": 6,
"type": "2",
}
]
I am trying to check each "SubType" in array1 and see if that element(id) is present in array2 and if present assign its "type" to a variable. "SubType" is | separated which I and converting to an array using array1..split('|'). This when assigning to a variable will need to be comma separated. I tried using array filter but I am not able to find a way to loop thorough the second array. Can anyone help?
array1.forEach(reg => {
if (reg.SubType) {
let subTypeTemp = reg.SubType.split('|');
let tempVariable = subTypeTemp.some(ele => {
let stringToassign = '';
for (let i = 0; i < array2.length; i++) {
if (ele == array2[i].id) {
stringToassign += array2[i].type + ",";
}
}
})
}
})
const array1 = [
{
SubType: "2|3|4|5|6"
},
{ SubType: "2" },
{ SubType: "3|4" },
{ SubType: "6" },
{ SubType: "3|6" }
];
const array2 = [
{
id: 2,
type: "1"
},
{ id: 3, type: "5" },
{ id: 4, type: "4" },
{ id: 5, type: "3" },
{ id: 6, type: "2" }
];
const array2Obj = array2.reduce(
(acc, curr) => ({
...acc,
[curr.id]: curr.type
}),
{}
);
const types = [];
array1.forEach(item => {
const sub_types = item.SubType.split("|");
sub_types.forEach(st => {
if (st in array2Obj) {
types.push(array2Obj[st]);
}
});
});
const types_str = [...new Set(types)].join(',');
console.log("types", types_str);
You could take a Map and prevent looping array2 over and over for getting type of a wanted id.
var array1 = [{ SubType: "2|3|4|5|6" }, { SubType: "2" }, { SubType: "3|4" }, { SubType: "6" }, { SubType: "3|6" }],
array2 = [{ id: 2, type: "1" }, { id: 3, type: "5" }, { id: 4, type: "4" }, { id: 5, type: "3" }, { id: 6, type: "2" }],
types = new Map(array2.map(({ id, type }) => [id.toString(), type])),
result = array1.map(({ SubType }) => SubType
.split('|')
.map(Map.prototype.get, types)
.join()
);
console.log(result);

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