JavaScript combine original object with sub array of objects - javascript

I have an large array of objects with some keys:values, one of the keys has a value that is an array of objects. I want to reduce the subarray into a new array of objects.
I can't figure out a solution using mapping thus far.
const warehouse = [
{
Server: 'EU',
Department: 'Paper',
Suppliers: [
{
Name: 'EU Paper',
Contract: 'Active'
},
{
Name: 'Local Tree',
Contract: 'Ended'
}
]
},
{
Server: 'US',
Department: 'Steel',
Suppliers: [
{
Name: 'Steel Research',
Contract: 'Active'
},
{
Name: 'Heat Vantage',
Contract: 'Active'
}
]
}
]
Output should be
const suppliers = [
{
Server: 'EU',
Department: 'Paper',
Name: 'EU Paper',
Contract: 'Active'
},
{
Server: 'EU',
Department: 'Paper',
Name: 'Local Tree',
Contract: 'Ended'
},
{
Server: 'US',
Department: 'Steel',
Name: 'Steel Research',
Contract: 'Active'
},
{
Server: 'US',
Department: 'Steel',
Name: 'Heat Vantage',
Contract: 'Active'
},
]
I can do this with basic JavaScript but i would like to see an option that optimizes for performance

You can use flatMap to loop thru the array and flat the result. Use map to loop thru the Suppliers array.
const warehouse = [{"Server":"EU","Department":"Paper","Suppliers":[{"Name":"EU Paper","Contract":"Active"},{"Name":"Local Tree","Contract":"Ended"}]},{"Server":"US","Department":"Steel","Suppliers":[{"Name":"Steel Research","Contract":"Active"},{"Name":"Heat Vantage","Contract":"Active"}]}];
let result = warehouse.flatMap(({Suppliers,...r}) => Suppliers.map(o => ({ ...o,...r})));
console.log(result);
You can also use concat and map
const warehouse = [{"Server":"EU","Department":"Paper","Suppliers":[{"Name":"EU Paper","Contract":"Active"},{"Name":"Local Tree","Contract":"Ended"}]},{"Server":"US","Department":"Steel","Suppliers":[{"Name":"Steel Research","Contract":"Active"},{"Name":"Heat Vantage","Contract":"Active"}]}];
let result = [].concat(...warehouse.map(({Suppliers,...r}) => Suppliers.map(o => ({ ...o,...r}))));
console.log(result);

Related

ReactJs How to map field of an array into a new array?

I'm new to javascript and react and working on my first mini project but I cannot figure out how to solve this.
In each "Order" model I have this array field called "orderItems" which holds an array of product models and within each product model there's a field called "shop". I want to get the "shop" value for all the products in orderItems and add it into a new array in "Order" model
Here is my schema:
{
orderItems: [
{
name: 'abc',
quantity: 1,
shop: 'abc',
image: '/images/cake.jpg',
price: 50,
_id: new ObjectId("62dbb03e90c4ca14ee3e06fd")
},
{
name: 'def',
quantity: 1,
shop: 'def',
image: '/images/cake.jpg',
price: 50,
_id: new ObjectId("62dbb03e90c4ca14ee3e06fd")
}
],
sellers: [],
shippingAddress: {
fullName: '123',
address: '123',
city: '123',
postalCode: '123',
country: '123'
},
paymentMethod: 'Paypal',
itemsPrice: 50,
shippingPrice: 10,
totalPrice: 60,
buyer: new ObjectId("62d6e48d8cd5cd2e62f6aeae"),
_id: new ObjectId("62dbcf88cf1bc8fc7b85cc0b"),
__v: 0
}
So I want to get 'abc', and 'def' from orderItems array and fill it in the sellers array. I can get the values but how do I fill it into the sellers array in the same model?
this is my api code
orderRouter.post(
'/', isAuth, expressAsyncHandler(async(req, res) => {
const newOrder = new Order({
orderItems: req.body.orderItems.map((x) => ({ ...x, product: x._id })),
shippingAddress: req.body.shippingAddress,
paymentMethod: req.body.paymentMethod,
itemsPrice: req.body.itemsPrice,
shippingPrice: req.body.shippingPrice,
totalPrice: req.body.totalPrice,
buyer: req.user._id,
sellers: [],
});
const order = await newOrder.save();
})
);
I tried using a loop and then order.sellers.push(order.orderItems[i].shop) and then saving again but its not updating in my database & in local storage the _id field was pushed into the array instead of shop
Step by step:
Declare a separated const orderItems above const newOrder.
Extract const sellers using a Set. You can also do it with an array or even an object, but Set will filter out duplicates automatically.
The create the order, using the 2 consts above, and remember to turn the Set into an array.
Should look something like this:
const orderItems = req.body.orderItems.map((x) => ({ ...x, product: x._id }));
const sellers = [...new Set(orderItems.map(x => x.shop))];
const newOrder = new Order({
orderItems,
sellers: [...sellers],
shippingAddress: req.body.shippingAddress,
paymentMethod: req.body.paymentMethod,
itemsPrice: req.body.itemsPrice,
shippingPrice: req.body.shippingPrice,
totalPrice: req.body.totalPrice,
buyer: req.user._id,
});
And here's a working example:
const orderItems = [{
name: 'P1',
quantity: 1,
shop: 'S1',
image: '/images/p1.jpg',
price: 50,
_id: '62dbb03e90c4ca14ee3e06fa',
}, {
name: 'P2',
quantity: 1,
shop: 'S2',
image: '/images/p2.jpg',
price: 50,
_id: '62dbb03e90c4ca14ee3e06fb',
}, {
name: 'P3',
quantity: 1,
shop: 'S1',
image: '/images/p3.jpg',
price: 50,
_id: '62dbb03e90c4ca14ee3e06fc',
}];
const sellersSet = new Set(orderItems.map(x => x.shop));
const sellersArray = [...sellersSet];
// See here why the conversion to array is needed:
console.log({ sellersSet, sellersArray });

fillter arrays of objects

i have two arrays.
const department = [
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' },
];
const models = [
{
id: '23',
name: 'model1',
departments: [{ id: '1', name: 'department1' }],
},
{
id: '54',
name: 'model2',
departments: [
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' },
],
},
];
i need to render accordions with department names and accordion details with matching models names. My question is how to filter those arrays to get models
We can map through the departments array, and add a models property that equals the models array, but filtered only to the ones that contain a matching department id.
const departments = [
{ id: "1", name: "department1" },
{ id: "2", name: "department2" },
];
const models = [
{
id: "23",
name: "model1",
departments: [{ id: "1", name: "department1" }],
},
{
id: "54",
name: "model2",
departments: [
{ id: "1", name: "department1" },
{ id: "2", name: "department2" },
],
},
];
const getDepartmentsWithModels = () => {
return departments.map((department) => {
return {
...department,
models: models.filter((model) => {
const modelDepartmentIds = model.departments.map(({ id }) => id);
return modelDepartmentIds.includes(department.id);
}),
};
});
};
console.log(getDepartmentsWithModels());
// [ { id: '1', name: 'department1', models: [ [Object], [Object] ] },
// { id: '2', name: 'department2', models: [ [Object] ] } ]```
I've built some code, which iterates over the departments. For each department it iterates the models and for each model it checks if the department is within the model departments.
const department =
[
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' }
]
const models =
[
{
id: '23',
name: 'model1',
departments: [{ id: '1', name: 'department1' }]
},
{
id: '54',
name: 'model2',
departments: [{ id: '1', name: 'department1' },{ id: '2', name: 'department2' }]
}
]
department.forEach( dep => {
console.log(`Department: ${dep.name}`)
models.forEach(model => {
if (model.departments.find(modelDep => dep.id===modelDep.id)) {
console.log(` Model: ${model.name}`)
}
})
})
If you could change your data objects, then your code could be much smoother.
I've changed your data objects slightly by just reducing the departments in a model to be an array of department id's. This code iterates over the departments. For each department it filters the models and iterates over the filtered models to output them to the console. This is lesser code and provides much better performance.
const department =
[
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' }
]
const models =
[
{
id: '23',
name: 'model1',
departments: ['1']
},
{
id: '54',
name: 'model2',
departments: ['1', '2']
}
]
department.forEach( dep => {
console.log(`Department: ${dep.name}`)
models.filter(model => model.departments.includes(dep.id)).forEach(model => {
console.log(` Model: ${model.name}`)
})
})
There are two solutions.
Using Array.reduce() --> returns an object where the key is department name and value is an array of the names of matching models:
let data1 = models.reduce((res, curr) => {
curr.departments.forEach(dep => {
if (!res[dep.name]) {
res[dep.name] = [curr.name]
} else {
if (!res[dep.name].includes(curr.name)) {
res[dep.name].push(curr.name);
}
}
})
return res;
}, {});
Using map and filter --> returns an array of kind:
[{department: [names of the models]},...]
let data2 = department.map(dep => {
let matchingModels = models.filter(model => {
return model.departments.filter(modDep => {
return modDep.name === dep.name;
}).length > 0;
}).map(mod => {
return mod.name;
});
return {
department: dep.name,
models: matchingModels
}
});

How to loop over a an array that is already inside a map function without duplicating the contents

I have the following code with the following arrays. I want to loop through both of them and pull out some data, and put them inside a final array. I am able to do that, but the contents are duplicated. I tried reading about reduce but don't quite understand it, and am not sure if it's the right solution. I have also setup a jsfiddle
https://jsfiddle.net/anders_kitson/Lcqn6fgd/
var lineItems = [{
id: 'li_1HyhAZHk5l44uIELgsMWqHqB',
object: 'item',
amount_subtotal: 7500,
amount_total: 7500,
currency: 'cad',
description: 'The Spencer',
price: [Object],
quantity: 1
},
{
id: 'li_1HyhAZHk5l44uIELeNUsiZPu',
object: 'item',
amount_subtotal: 7500,
amount_total: 7500,
currency: 'cad',
description: 'The Gertie',
price: [Object],
quantity: 1
}
]
var arr = [{
id: 'prod_IS1wY1JvSv2CJg',
object: 'product',
active: true,
attributes: [],
created: 1606248785,
description: 'Shelf Set',
images: [
'https://files.stripe.com/links/fl_test_raNEqk9ZhzX3WdQsnvXX4gFq'
],
livemode: false,
metadata: {},
name: 'The Spencer',
statement_descriptor: null,
type: 'service',
unit_label: null,
updated: 1606248785
},
{
id: 'prod_IS299dMnC13Ezo',
object: 'product',
active: true,
attributes: [],
created: 1606249543,
description: 'Shelf Set',
images: [
'https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe'
],
livemode: false,
metadata: {},
name: 'The Gertie',
statement_descriptor: null,
type: 'service',
unit_label: null,
updated: 1606249543
}
];
let productArr = [];
arr.map((item) => {
lineItems.map((line) => {
productArr.push({
image: item.images[0],
name: item.name,
price: line.amount_total,
});
});
});
console.log(productArr);
This is the output I get where you can see the array repeats the values, and I know I have coded it this way I just don't know how to fix it.
[{
image: "https://files.stripe.com/links/fl_test_raNEqk9ZhzX3WdQsnvXX4gFq",
name: "The Spencer",
price: 7500
}, {
image: "https://files.stripe.com/links/fl_test_raNEqk9ZhzX3WdQsnvXX4gFq",
name: "The Spencer",
price: 7500
}, {
image: "https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe",
name: "The Gertie",
price: 7500
}, {
image: "https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe",
name: "The Gertie",
price: 7500
}]
To Be more clear this is the output that I want
[{
image: "https://files.stripe.com/links/fl_test_raNEqk9ZhzX3WdQsnvXX4gFq",
name: "The Spencer",
price: 7500
}, {
image: "https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe",
name: "The Gertie",
price: 7500
},
]
I have tried the suggestion in the comments with the following
let b
arr.map((item) => {
b = lineItems.map((line) => {
return {
image: item.images[0],
name: item.name,
price: line.amount_total,
};
});
});
but it returns the same ones twice
[{
image: "https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe",
name: "The Gertie",
price: 7500
}, {
image: "https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe",
name: "The Gertie",
price: 7500
}]
Although not expressed directly in your question, it seems you're looking to do a join in javascript. The only things I see relating the two are 'name' in products and 'description' in the line items. So do a loop join on that.
Here's some sample code using your example but stripped down only to what's relevant:
var lineItems = [
{ amount_total: 7500, description: 'The Spencer' },
{ amount_total: 7500, description: 'The Gertie' }
]
var arr = [
{ images: ['Spencer Image 1'], name: 'The Spencer' },
{ images: ['Gertie Image 1'], name: 'The Gertie' }
]
let joined = arr
.flatMap(a => lineItems.map(li => ({a, li})))
.filter(obj => obj.a.name == obj.li.description)
.map(obj => ({
image: obj.a.images[0],
name: obj.a.name,
price: obj.li.amount_total
}));
console.log(joined);
Being a loop join, it may not be that efficient. To do a hash join is a little more involved. You can look through the source code of my developing project
fluent-data, or it might even be useful to you to use it directly if you can follow the documentation.
You can use a single map call and reference your second lineItems array either by index, if you know that the two arrays are the same length and order
const output = arr.map((o, i) => ({
name: o.name,
image: o.images[0],
price: lineItems[i].amount_total}
));
or by using find() to retrieve the relevant object.
const outputUsingFind = arr.map(o => {
const lineItem = lineItems.find(item => item.description === o.name);
// ** add lineItem valid check here **
return {
name: o.name,
image: o.images[0],
price: lineItem.amount_total};
});
var lineItems = [{amount_subtotal: 7500,amount_total: 700,description: 'The Spencer',},{amount_subtotal: 7500,amount_total: 500,description: 'The Gertie',}];
var arr = [{images: ['spencer image'],name: 'The Spencer',},{images: ['gertie image'],name: 'The Gertie'}];
// since your arrays are ordered the same you can access the second object using
// the index passed from map.
const output = arr.map((o, i) => ({
name: o.name,
image: o.images[0],
price: lineItems[i].amount_total}
));
console.log(output);
// if the two arrays are not in the same order you can use find() to retrieve
// the second object by property (you'll need to check
const outputUsingFind = arr.map(o => {
const lineItem = lineItems.find(item => item.description === o.name);
// ** add lineItem valid check here **
return {
name: o.name,
image: o.images[0],
price: lineItem.amount_total};
});
console.log(outputUsingFind);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to re-map an array to use in Ant Design Tree

I have this code structure:
const data = [
{
name: 'Lebron',
sports: 'Basketball',
},
{
name: 'Durant',
sports: 'Basketball',
},
{
name: 'Federrer',
sports: 'Tennis',
},
{
name: 'Nadal',
sports: 'Tennis',
},
];
and I'm trying to transform it into this:
const treeData = [
{
title: 'Select All',
key: 'All',
children: [
{
title: 'Basketball',
key: 'Basketball',
children: [
{
title: 'Lebron',
key: 'Lebron',
},
{
title: 'Durant',
key: 'Durant',
},
],
},
{
title: 'Tennis',
key: 'Tennis',
children: [
{
title: 'Federrer',
key: 'Federrer',
},
{
title: 'Nadal',
key: 'Nadal',
},
],
},
],
},
];
to use in Ant Design Tree link.
Right now my plan is to get all the sports like this:
let sports = data.map(({ sports }) => sports);
sports = [...new Set(sports)];
but after that I have no idea on what should be my next step to achieve the treeData
You can use .reduce() to accumulate all sports to a Map, where each sport is a key and each value for the sport is an array of associated names. Once you have built the Map, you can use Array.from() to convert your map into your desired children array. This can be done by providing a mapping function as the second argument to Array.from(), and using it to convert each entry ([key, value]) pair to an object of your desired structure.
See example below:
const data = [ { name: 'Lebron', sports: 'Basketball', }, { name: 'Durant', sports: 'Basketball', }, { name: 'Federrer', sports: 'Tennis', }, { name: 'Nadal', sports: 'Tennis', }, ];
const children = Array.from(data.reduce((m, {name, sports}) =>
m.set(sports, [...(m.get(sports) || []), name])
, new Map), ([key, arr]) => ({title: key, key, children: arr.map(title => ({title, key: title}))}));
const treeData = [{title: 'Select All', key: 'All', children}];
console.log(treeData);
const data = [
{
name: 'Lebron',
sports: 'Basketball',
},
{
name: 'Durant',
sports: 'Basketball',
},
{
name: 'Federrer',
sports: 'Tennis',
},
{
name: 'Nadal',
sports: 'Tennis',
},
];
const dataMap: Record<string, string[]> = {};
data.forEach(({ name, sports }) => {
dataMap[sports] ??= []
dataMap[sports].push(name)
})
const treeData = [{
title: 'Select All',
key: 'All',
children: Object.entries(dataMap).map(([sport, names]) => ({
title: sport,
key: sport,
children: names.map(name => ({
title: name,
key: name
}))
}))
}]
Here's a two-steps way of get the children of your final object:
First, use a reduce to create an object of sports/children, where children matches what you have in your final output:
const middle = data.reduce((transformed, item) => {
if (!transformed[item.sports]) {
transformed[item.sports] = [];
}
transformed[item.sports].push({
title: item.name,
key: item.name
});
return transformed;
}, {});
Then map the results of the reduce function to reshape the object:
const results = Object.entries(middle).map(([key, children]) => ({
title: key,
key,
children
}));

filter array of objects from string name to id

I am trying to convert an array of objects containing string values to their id value based off other array of objects. Here are the arrays.
const employees = [
{
name: 'bob',
department: 'sales',
location: 'west'
},
{
name:'fred',
department: 'sales',
location: 'west'
},
{
name:'josh',
department: 'inventory',
location: 'east'
},
{
name: 'mike',
department: 'quality assurance',
location: 'north'
}
];
const departments = [
{
dep: 'sales',
id: 12
},
{
dep:'quality assurance',
id: 11
},
{
dep:'inventory',
id: 13
}
];
const locations = [
{
region: 'west',
id: 3
},
{
region:'north',
id: 1
},
{
region:'east',
id: 2
},
{
region:'south',
id: 4
}
];
I would like the converted employees array to look like this:
[
{name:"bob", department: 12, location: 3},
{name:"fred", department: 12, location: 3},
{name:"josh", department: 13, location: 2},
{name:"mike", department: 11, location: 1}
]
I've tried:
employees.forEach((row) => {
row.department = departments.filter(depart => row.department === depart.dep)
.reduce((accumulator, id) => id)
row.department = row.department.id; // would like to remove this.
});
employees.forEach((row) => {
row.location = locations.filter(loc => row.location === loc.region)
.reduce((accumulator, id) => id);
row.location = row.location.id; // would like to remove this part.
});
I get the desired results from using the forEach I have, but I think there is a better way of using .filter() and .reduce(). I would like help removing the last line of the two forEach statements where I have to set row.department = row.department.id and row.location = row.location.id
One possible approach:
const dehydratedEmployees = employees.map(emp => {
const depId = departments.find(dep => dep.dep === emp.department).id;
const locId = locations.find(loc => loc.location === loc.region).id;
return { name: emp.name, department: depId, location: locId };
});
In other words, you can use Array.prototype.find() instead of filter-reduce combo. As .reduce() won't stop at the first successful search, .find() is both more efficient and concise. Just don't forget to apply polyfill for IE and other non-supportive browsers.
One solution is to create Map for departments and locations to eliminated nested loop when mapping employees.
Map can be created from a nested array: new Map([[key, value], [key, value]]):
const employees = [
{ name: 'bob', department: 'sales', location: 'west' },
{ name:'fred', department: 'sales', location: 'west' },
{ name:'josh', department: 'inventory', location: 'east' },
{ name: 'mike', department: 'quality assurance', location: 'north'}
];
const departments = [
{ dep: 'sales', id: 12 },
{ dep:'quality assurance', id: 11 },
{ dep:'inventory', id: 13}
];
const locations = [
{ region: 'west', id: 3 },
{ region:'north', id: 1},
{ region:'east', id: 2 },
{ region:'south', id: 4}
];
const departmentMap = new Map(departments.map(i => [i.dep, i.id]));
const locationMap = new Map(locations.map(i => [i.region, i.id]));
const result = employees.map(e => ({
name: e.name,
department: departmentMap.get(e.department),
location: locationMap.get(e.location)
}))
console.log(result);
Another possible approach. You can use Array.prototype.filter()(like below)
const employees=[{name:'bob',department:'sales',location:'west'},{name:'fred',department:'sales',location:'west'},{name:'josh',department:'inventory',location:'east'},{name:'mike',department:'quality assurance',location:'north'}];const departments=[{dep:'sales',id:12},{dep:'quality assurance',id:11},{dep:'inventory',id:13}];const locations=[{region:'west',id:3},{region:'north',id:1},{region:'east',id:2},{region:'south',id:4}]
var newArray=employees.map((x)=>{
return { name: x.name,
department: departments.filter(y=>y.dep === x.department)[0].id,
location: locations.filter(y=>y.region===x.location)[0].id};
});
console.log(newArray);

Categories

Resources