I am currently struggling with the formatting of a map operation on two object arrays in Javascript.
So lets say we have two arrays:
var customer = [
{ "Name": "Thomas", "Address": "example street 34", "customerID": 1 },
{ "Name": "Alica", "Address": "example street 24", "customerID": 2 },
{ "Name": "John", "Address": "example bouelvard 4", "customerID": 3 }
]
var orders = [
{ "Product": "iPhone 12", "Amount": 2, "customerID": 1 },
{ "Product": "charger", "Amount": 1, "customerID": 1 },
{ "Product": "screen protection", "Amount": 5, "customerID": 2 }
]
I want to have a result array so that when I print it out, I have an overview over customers with their orders in this way:
{
customer: {
"Name": "Thomas",
"Address": "example street 34",
"customerID": 1,
},
order: [
{
"Product": "iPhone 12",
"Amount": 2,
"customerID": 1
},
{
"Product": "charger",
"Amount": 1,
"customerID": 1
}
]
}
So I basically did a map function and searched for orders with the same customer id.
let overview = customers.map(element1 => ({ ...element1, : [...(orders.filter(element => element.customerID === element1.customerID))] }));
This is what I get:
{
"Name": "Thomas",
"Address": "example street 34",
"customerID": 1,
"order": [[Object], [Object]]
}
How do I get the "customer:" before the output of the customer objects and why do I get the Object output in my order array?
try with that:
customer.map(element1 => (
{...element1, order: orders.filter(element => element.customerID === element1.customerID)}
)))
You were missing 'order' key and also don't need to spread since filter returns an array (empty if nothing filtered)
You're almost there:
let overview = customer.map(customer => ({
customer,
order: orders.filter(order => customer.customerID === order.customerID)
}))
You are so close! So very close. You can rename customer to customers just to avoid confusion the element1 becomes customer and the rest is as shown below:
const
customers = [ { "Name": "Thomas", "Address": "example street 34", "customerID": 1 }, { "Name": "Alica", "Address": "example street 24", "customerID": 2 }, { "Name": "John", "Address": "example bouelvard 4", "customerID": 3 } ],
orders = [ { "Product": "iPhone 12", "Amount": 2, "customerID": 1 }, { "Product": "charger", "Amount": 1, "customerID": 1 }, { "Product": "screen protection", "Amount": 5, "customerID": 2 } ],
custOrders = customers
.map(
customer =>
({
customer,
orders:orders
.filter(order => order.customerID === customer.customerID)
})
);
console.log( custOrders );
First you can create map from orders array where customerId will be key and array with all orders belonging to one customer will be map value.
Next is initialising empty results array and iterating customers array. While iterating, push new object to results array. New object should contain 2 fields, customer and order. customer is object from iteration and order is map value which you get from previously generate map using customer.customerID as map key.
This way, performance are increased because fetching data from Map data structure has O(1) time complexity. Using filter method to find all orders for specific customer is time consuming with complexity O(n).
const customers = [{"Name": "Thomas", "Address": "example street 34", "customerID": 1},{"Name": "Alica", "Address": "example street 24", "customerID": 2}, {"Name": "John", "Address": "example boulevard 4", "customerID": 3}];
const orders = [{"Product": "iPhone 12", "Amount": 2, "customerID": 1},{"Product": "charger", "Amount": 1, "customerID": 1},{"Product": "screen protection", "Amount": 5, "customerID": 2}];
const ordersMap = new Map();
for (const order of orders) {
const { customerID } = order;
const mapValue = ordersMap.get(customerID);
if (mapValue) {
mapValue.push(order);
} else {
ordersMap.set(customerID, [order]);
}
}
const results = [];
for (const customer of customers) {
results.push({
customer,
order: ordersMap.get(customer.customerID),
});
}
console.log(results)
Related
I'm trying to filter some objects based on another array of objects. So I'm getting data from an API. These are for example receipts:
[
{
"id": 1,
"name": "test",
"category": {
"id": 1,
"name": "Cookies",
},
},
{
"id": 2,
"name": "test2",
"category": {
"id": 2,
"name": "Candy",
},
}
]
Then I'm trying to filter the objects on the category name based on another array of categories.
I've created a function for this:
function onSelectCategory(category) {
let receiptsList = receipts.filter((a) =>
a.category.includes(category.name)
);
setReceiptsView(receiptsList);
setSelectedCategory(category);
}
const category = [ { "id": 2, "name": "Candy" } ];
onSelectCategory(category);
When I run this function, I get an empty Array []. I can't really figure out what I'm doing wrong.
Since the param seems to be an array of objects, you need to use Array#some for comparison instead:
const receipts = [
{ "id": 1, "name": "test", "category": { "id": 1, "name": "Cookies" } },
{ "id": 2, "name": "test2", "category": { "id": 2, "name": "Candy" } }
];
const categories = [ { "id": 2, "name": "Candy" } ];
const receiptsList = receipts.filter(({ category }) =>
categories.some(({ name }) => name === category.name)
);
console.log(receiptsList);
Another solution using Set:
const receipts = [
{ "id": 1, "name": "test", "category": { "id": 1, "name": "Cookies" } },
{ "id": 2, "name": "test2", "category": { "id": 2, "name": "Candy" } }
];
const categories = [ { "id": 2, "name": "Candy" } ];
const categorySet = new Set(categories.map(({ name }) => name));
const receiptsList = receipts.filter(({ category }) =>
categorySet.has(category.name)
);
console.log(receiptsList);
Assuming that category (the parameter) is a string, the issue is that you are attempting to get the attribute name from the string, when you should be comparing the string to the object.
Try this:
a.category.name == category;
instead of
a.category.includes(category.name)
I may be wrong aboout assuming that category is a string, please clarify by telling us what the parameter category is equal to.
Trying to create a new object, and then join all the objects from nested values from a JSON file.
The JSON data is rather large, so have taken a sample, and called it var items
Problem I am having is that the nested data is not updating the new object.
var items = [
{
"id": 11,
"title": "Fruit Test",
"releaseDateTime": "2021-10-21T10:50:00+09:30",
"mainContent": "Fruit order for 1 person",
"storeNames": [
"Store 1"
],
"items": [
{
"itemName": "Melon",
"otherName": "Watermelon"
},
{
"itemName": "Apple",
"otherName": "Red apple"
}
]
},
{
"id": 12,
"title": "Canned Test",
"releaseDateTime": "2021-10-21T10:50:00+09:30",
"mainContent": "Canned order for 2 people",
"storeNames": [
"Store 1"
],
"items": [
{
"itemName": "Tomatoes",
"otherName": "Diced tomato"
}
]
},
{
"id": 13,
"title": "Dairy Test",
"releaseDateTime": "2021-10-21T10:50:00+09:30",
"mainContent": "Dairy Order for 2 people",
"storeNames": [
"Store 1"
],
"items": []
}
]
;
var copyItems = [];
for (let i = 0; i < items.length; i++) {
items[i].allItems = items[i].items;
copyItems.push(items[i])
}
console.log(copyItems);
var copyItems = copyItems.map(function(elem){
return elem.allItems;
}).join(",");
console.log(`These are the final items ${copyItems}`);
I am able to create the new object, and add the nested arrays to this. However I am trying to get the allItems object to display the information like the following:
[
{
"id": 11,
"allItems": "Melon, Apple",
"title": "Fruit Test",
"releaseDateTime": "2021-10-21T10:50:00+09:30",
"mainContent": "Fruit order for 1 person",
"storeNames": [
"Store 1"
],
"items": [
{
"itemName": "Melon",
"otherName": "Watermelon"
},
{
"itemName": "Apple",
"otherName": "Red apple"
}
]
},
{
"id": 12,
"allItems": "Tomatoes",
"title": "Canned Test",
"releaseDateTime": "2021-10-21T10:50:00+09:30",
"mainContent": "Canned order for 2 people",
"storeNames": [
"Store 1"
],
"items": [
{
"itemName": "Tomatoes",
"otherName": "Diced tomato"
}
]
},
{
"id": 13,
"allItems": "",
"title": "Dairy Test",
"releaseDateTime": "2021-10-21T10:50:00+09:30",
"mainContent": "Dairy Order for 2 people",
"storeNames": [
"Store 1"
],
"items": []
}
]
Here is my JSFiddle: https://jsfiddle.net/buogdvx9/6/
Javascript is still a language I am learning and working through, and some things still catch me out.
Thank you.
You can use Array.map() to create the new array, then using some destructuring to create each new element in this array.
We create the allitems property on each new element by first mapping the sub items array to get a list of subitem names, then using Array.join() to create a comma separated string.
The arrow function you see as the first argument to Array.map is another way of writing function(args) { .. }.
const items = [ { "id": 11, "title": "Fruit Test", "releaseDateTime": "2021-10-21T10:50:00+09:30", "mainContent": "Fruit order for 1 person", "storeNames": [ "Store 1" ], "items": [ { "itemName": "Melon", "otherName": "Watermelon" }, { "itemName": "Apple", "otherName": "Red apple" } ] }, { "id": 12, "title": "Canned Test", "releaseDateTime": "2021-10-21T10:50:00+09:30", "mainContent": "Canned order for 2 people", "storeNames": [ "Store 1" ], "items": [ { "itemName": "Tomatoes", "otherName": "Diced tomato" } ] }, { "id": 13, "title": "Dairy Test", "releaseDateTime": "2021-10-21T10:50:00+09:30", "mainContent": "Dairy Order for 2 people", "storeNames": [ "Store 1" ], "items": [] } ];
const result = items.map(({ id, ...rest}) => {
return {
id,
allItems: rest.items.map(el => el.itemName).join(', '),
...rest
};
});
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Since you only want to update the existing object, using forEach to loop over each item in array, then loop over the prosperity with a map operator to get the array with itemName.
items.forEach((obj) => {
obj.allItems = obj.items.map((item) => item.itemName)
});
console.log(items)
Simple example:
// iterating over the items
for (let i = 0; i < items.length; i++) {
let currentItem = items[i];
currentItem.allItems = []; // adding new empty array
for (let j = 0; j < currentItem.items.length; j++) {
currentItem.allItems.push(currentItem.items[j].itemName);
}
}
Working Example:
var items = [
{
"id": 11,
"title": "Fruit Test",
"releaseDateTime": "2021-10-21T10:50:00+09:30",
"mainContent": "Fruit order for 1 person",
"storeNames": [
"Store 1"
],
"items": [
{
"itemName": "Melon",
"otherName": "Watermelon"
},
{
"itemName": "Apple",
"otherName": "Red apple"
}
]
},
{
"id": 12,
"title": "Canned Test",
"releaseDateTime": "2021-10-21T10:50:00+09:30",
"mainContent": "Canned order for 2 people",
"storeNames": [
"Store 1"
],
"items": [
{
"itemName": "Tomatoes",
"otherName": "Diced tomato"
}
]
},
{
"id": 13,
"title": "Dairy Test",
"releaseDateTime": "2021-10-21T10:50:00+09:30",
"mainContent": "Dairy Order for 2 people",
"storeNames": [
"Store 1"
],
"items": []
}
]
;
// since you only want to update the existing object, using map to loop over each item in array
items.forEach((obj) => {
// using map to create the new array of just itemNames
obj.allItems = obj.items.map((item) => item.itemName)
});
console.log(items)
Just use the combination of Array.map and Array.join
Logic
Since you want to create a new array, run Array.map on the parent array.
On each nodes in the parent return the whole node with one extra key allItems.
For allItems create a new array from items array in each node and join then with space
var items = [{"id":11,"title":"Fruit Test","releaseDateTime":"2021-10-21T10:50:00+09:30","mainContent":"Fruit order for 1 person","storeNames":["Store 1"],"items":[{"itemName":"Melon","otherName":"Watermelon"},{"itemName":"Apple","otherName":"Red apple"}]},{"id":12,"title":"Canned Test","releaseDateTime":"2021-10-21T10:50:00+09:30","mainContent":"Canned order for 2 people","storeNames":["Store 1"],"items":[{"itemName":"Tomatoes","otherName":"Diced tomato"}]},{"id":13,"title":"Dairy Test","releaseDateTime":"2021-10-21T10:50:00+09:30","mainContent":"Dairy Order for 2 people","storeNames":["Store 1"],"items":[]}];
const newItems = items.map(node => ({ ...node, allItems: node.items.map(item => item.itemName).join(" ")}));
console.log(newItems);
I have a json and would like to filter for one key multiple attribites as exact match.
I tried the following:
let data = [{
"name": "Product 2",
"link": "/stock/product2",
"category": "234",
"description": ""
}, {
"name": "Product 1",
"link": "/stock/product1",
"category": "1231",
"description": ""
}, {
"name": "Product 3",
"link": null,
"category": "22",
"description": ""
}]
data = data.filter(cv => cv.category === ["22", "234"]);
console.log(JSON.stringify(data))
I would like to get back the object with the name: Product 2 and name: Product 3.
Any suggestions why I get [] back?
I appreciate your replies!
Consider using Set.has for your desired attributes, so you can can have O(1) lookup time rather than the O(n) (where n is the number of desired attributes) lookup time using Array.includes.
As a result, if you use a set the overall the time complexity for the whole filter line will be O(m) (where m is the number of objects in data) rather than O(mn) if you used Array.includes or had multiple if-else / or conditions to check for each desired attribute:
const data = [
{
name: "Product 2",
link: "/stock/product2",
category: "234",
description: "",
},
{
name: "Product 1",
link: "/stock/product1",
category: "1231",
description: "",
},
{
name: "Product 3",
link: null,
category: "22",
description: "",
},
];
const desiredCategories = new Set(["22", "234"]);
const filteredData = data.filter(cv => desiredCategories.has(cv.category));
console.log(JSON.stringify(filteredData, null, 2));
You are comparing a single value against an array of values. One solution would be to check for one value or (||) the other.
data = data.filter(cv => cv.category === "22" || cv.category === "234");
This can be achieved by the includes method.
let data = [{
"name": "Product 2",
"link": "/stock/product2",
"category": "234",
"description": ""
}, {
"name": "Product 1",
"link": "/stock/product1",
"category": "1231",
"description": ""
}, {
"name": "Product 3",
"link": null,
"category": "22",
"description": ""
}]
data = data.filter(cv => ["22", "234"].includes(cv.category));
console.log(JSON.stringify(data))
Besides, I think this is easy to read/understand.
Check if the item is in the array instead
data = data.filter(cv => ["22", "234"].includes(cv.category));
const data = [{
"name": "Product 2",
"link": "/stock/product2",
"category": "234",
"description": ""
}, {
"name": "Product 1",
"link": "/stock/product1",
"category": "1231",
"description": ""
}, {
"name": "Product 3",
"link": null,
"category": "22",
"description": ""
}]
const filteredData = data.filter(({ category }) => category === "22" || category === "234");
console.log(JSON.stringify(filteredData))
I wouldn't mutate your original object. Also, you can deconstruct category in the filter function.
Looking for a more "functional" way of achieving this...
I have an object of products that looks like this (note duplicate skuid's)
"products": [
{
"skuid": "B1418",
"name": "Test Product 1",
"price": 7,
"lastOrderedDate": 20181114,
"quantity": 2
},{
"skuid": "B3446",
"name": "Test Product 2",
"price": 6,
"lastOrderedDate": 20190114,
"quantity": 2
},{
"skuid": "B1418",
"name": "Test Product 1",
"price": 7,
"lastOrderedDate": 20180516,
"quantity": 5
},{
"skuid": "B3446",
"name": "Test Product 2",
"price": 6,
"lastOrderedDate": 20180411,
"quantity": 11
}
]
I want to create a new array that has a single object for each distinct skuid but that SUMS all the quantity values and retains the newest lastOrderedDate.
The final result would look like:
"products": [
{
"skuid": "B1418",
"name": "Test Product 1",
"price": 7,
"lastOrderedDate": 20181114,
"quantity": 7
},{
"skuid": "B3446",
"name": "Test Product 2",
"price": 6,
"lastOrderedDate": 20190114,
"quantity": 13
}
]
I can do it with a bunch of forEach's and if's, but I'd like to learn a more concise way to do it. Perhaps with a sort, then reduce?
You can do that in following steps:
Create an object using reduce() whose keys will unique skuid and value will fist object with that skuid
Use forEach on the array and increase the quantity property of corresponding object in object created object.
Use Object.values() to get an array.
const products = [ { "skuid": "B1418", "name": "Test Product 1", "price": 7, "lastOrderedDate": 20181114, "quantity": 2 },{ "skuid": "B3446", "name": "Test Product 2", "price": 6, "lastOrderedDate": 20190114, "quantity": 2 },{ "skuid": "B1418", "name": "Test Product 1", "price": 7, "lastOrderedDate": 20180516, "quantity": 5 },{ "skuid": "B3446", "name": "Test Product 2", "price": 6, "lastOrderedDate": 20180411, "quantity": 11 } ]
const res = products.reduce((ac,a) => (!ac[a.skuid] ? ac[a.skuid] = a : '',ac),{})
products.forEach(x => res[x.skuid].quantity += x.quantity)
console.log(Object.values(res))
You could take a Map and get all values later as result set.
const
getGrouped = (m, o) => {
var item = m.get(o.skuid);
if (!item) return m.set(o.skuid, Object.assign({}, o));
if (item.lastOrderedDate < o.lastOrderedDate) item.lastOrderedDate = o.lastOrderedDate;
item.quantity += o.quantity;
return m;
};
var data = { products: [{ skuid: "B1418", name: "Test Product 1", price: 7, lastOrderedDate: 20181114, quantity: 2 }, { skuid: "B3446", name: "Test Product 2", price: 6, lastOrderedDate: 20190114, quantity: 2 }, { skuid: "B1418", name: "Test Product 1", price: 7, lastOrderedDate: 20180516, quantity: 5 }, { skuid: "B3446", name: "Test Product 2", price: 6, lastOrderedDate: 20180411, quantity: 11 }] },
result = Array.from(data.products
.reduce(getGrouped, new Map)
.values()
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do it like this:
const DATA = [
{
"skuid": "B1418",
"name": "Test Product 1",
"price": 7,
"lastOrderedDate": 20181114,
"quantity": 2
},{
"skuid": "B3446",
"name": "Test Product 2",
"price": 6,
"lastOrderedDate": 20190114,
"quantity": 2
},{
"skuid": "B1418",
"name": "Test Product 1",
"price": 7,
"lastOrderedDate": 20180516,
"quantity": 5
},{
"skuid": "B3446",
"name": "Test Product 2",
"price": 6,
"lastOrderedDate": 20180411,
"quantity": 11
}
];
const mergeStrategy = {
quantity: (a, b) => a + b,
lastOrderedDate: (a, b) => Math.max(a, b)
}
const mergeByStrategy = strat => a => b => {
const c = Object.assign({}, a, b);
return Object.keys(strat).reduce((acc, k) => {
acc[k] = strat[k](a[k], b[k]);
return acc;
}, c);
}
const groupByWith = prop => merge => xs => xs.reduce((acc, x) => {
if (acc[x[prop]] === void 0) { acc[x[prop]] = x; }
else { acc[x[prop]] = merge(acc[x[prop]])(x); }
return acc;
}, {});
const mergeFunc = mergeByStrategy(mergeStrategy);
const groupData = groupByWith('skuid')(mergeFunc);
console.log(Object.values(groupData(DATA)))
mergeStrategy defines how certain properties are merged. You can define as much properties/functions here as you like.
The mergeByStrategy function first takes a strategy (see above), and then two objects to merge. Note that it takes the objects in a curried form. It creates a shallow copy of both objects which it modifies according to the given strategy. This ensures your original data is still intact.
The groupByWith function takes a property name, a merging function and an array of objects and creates a dictionary/POJO/hashmap/call-as-you-like where each object is stored by the given property name. If there already exists an entry in the dictionary, it uses the merging function to combine the existing entry with the new entry, otherwise is simply stores the new entry into the object.
It's confusing to try to explain, but I want to be able to add a list of skill sets to different people objects that are in their own list.
For example:
I have a Json object of people:
"people": [
{
"id": 1,
"name": "Tony Rogers",
},
{
"id": 2,
"name": "Steven Grant",
},
{
"id": 3,
"name": "Peter Wilson",
},
]
and then I have a list of skills that I want to match up with them:
"skills": [
{
"id": 1,
"name": "Engineering",
"personId": 1
},
{
"id": 2,
"name": "Painting",
"personId": 2
},
{
"id": 3,
"name": "Chemistry",
"personId": 3
},
{
"id": 4,
"name": "Physics",
"personId": 1
},
]
but I am unsure how to get the output I want by looping through both lists. I would preferably like to append a "skills" section onto each person that contains all of their skills.
I thought I could do something along the lines of
people.forEach(function(person){
skills.forEach(function(skill){
if(skill.personId == person.id){
person['skills'] = {"name" : skill.name};
}
});
});
but it repeats a person multiple times rather than adding to their own skill list.
You need an array type to store multiple skills, so instead of just assigning person['skills'] = {"name" : skill.name}; create an array and push the new skill object to it.
people.forEach(function(person){
skills.forEach(function(skill){
if(skill.personId == person.id){
//creates an array, if not yet created
person['skills'] = person['skills'] || [];
//push the skill object to the array
person['skills'].push(skill.name);
}
});
});
If you have 20 persons and 20 skills, then it will be 20 * 20 = 400 loops!
You can do it more efficiently using just 2 loops:
var skillsByPerson = {};
skills.forEach(function(skill) {
var personId = skill.personId;
var personSkills = skillsByPerson[personId] || (skillsByPerson[personId] = []);
personSkills.push({ name: skill.name });
});
people.forEach(function(person) {
person.skills = skillsByPerson[person.id] || [];
});
Here is the jsPerf test proof for performance check.
You're overwriting skills on each iteration (this part: person['skills'] = {"name" : skill.name};), instead you need to push a skill into an array of skills:
var people = [
{"id": 1, "name": "Tony Rogers",},
{"id": 2, "name": "Steven Grant",},
{"id": 3, "name": "Peter Wilson",}];
var skills = [{
"id": 1,
"name": "Engineering",
"personId": 1
}, {
"id": 2,
"name": "Painting",
"personId": 2
}, {
"id": 3,
"name": "Chemistry",
"personId": 3
}, {
"id": 4,
"name": "Physics",
"personId": 1
}, ]
people.forEach(function(person) {
person['skills'] = []; // create an empty skills array for each person
skills.forEach(function(skill) {
if (skill.personId == person.id) {
person['skills'].push({"name": skill.name}); // push the skill
}
});
});
console.log(people);