Group nested objects with reduce - javascript

I have an array with objects
const data = [{
"id": 19887003,
"category": "Shops",
"details": "Shoe Store",
"star": 2,
"subCategory": "Outlet",
},
{
"id": 19234003,
"category": "Shops",
"details": "Shoe Point",
"star": 2,
"subCategory": "Outlet",
},
{
"id": 190456003,
"category": "Food",
"details": "Korean",
"star": 4,
"subCategory": "Restaurant",
},
{
"id": 190111003,
"category": "Food",
"details": "Chinese",
"star": 4,
"subCategory": "Restaurant",
},
{
"id": 1902303,
"category": "Food",
"details": "Lounge",
"star": 4,
"subCategory": "Bar",
}]
this is a small piece but the structure is the same for all objects: i have a category, with multiple subcategories and sometimes the subcategory has details..for example the category food has the subcategory restourant and restourant has many types (chinese, korean).
My goal is to get a structure like that:
[
{
"category": "Food",
"subCategories": [
{
"Subcategory": "Bar",
"details": [
{
name: "Lounge",
star: 2,
id: 1902303
}
]
},
{
"Subcategory": "Restaurant",
"details": [
{
name: "Chinese",
star: 4,
id: 190111003
},
{
name: "Korean",
star: 4,
id: 190456003
}
]
}
},
{
"category": "Shops",
"subCategories": [
{
"Subcategory": "Outlet",
"details": [
{
name: "Shoe Store",
star: 2,
id: 19887003
},
{
name: "Shoe Point",
star: 2,
id: 19234003
}
]
}
]
}
]
My attempt:
const groupedCategories = data.reduce((accumulator, element) => {
const detail = element.details;
const category = element.category;
const subCategory = element.subCategory;
if (accumulator[category]){
return {
...accumulator,
[category]: {
...accumulator[category],
subCategories: [...new Set([...accumulator[category].subCategories,subCategory])],
}
}}
else {
return {
...accumulator,
[category]: {
subCategories: [subCategory],
}
}
}
}, {});
I tried use reduce method like that but this is not the exact structure I desire in particular how to put details fields into subcategories.
Thanks

array.reduce seems to be the right choice. Simplest approach is to have double if statement to check if previous element (category and subcategory exists) and either push into existing array or create new object on upper level:
const data = [{
"id": 19887003,
"category": "Shops",
"details": "Shoe Store",
"star": 2,
"subCategory": "Outlet",
},
{
"id": 19234003,
"category": "Shops",
"details": "Shoe Point",
"star": 2,
"subCategory": "Outlet",
},
{
"id": 190456003,
"category": "Food",
"details": "Korean",
"star": 4,
"subCategory": "Restaurant",
},
{
"id": 190111003,
"category": "Food",
"details": "Chinese",
"star": 4,
"subCategory": "Restaurant",
},
{
"id": 1902303,
"category": "Food",
"details": "Lounge",
"star": 4,
"subCategory": "Bar",
}]
let output = data.reduce((acc,cur) => {
let {category, subCategory, ...rest} = cur;
let prevCat = acc.find(x => x.category === category);
if(!prevCat){
acc.push({category, subCategories: [{subCategory, details: [rest]}]});
} else {
let prevSubCat = prevCat.subCategories.find(x => x.subCategory === subCategory);
if(!prevSubCat) {
prevCat.subCategories.push({subCategory, details: [rest]});
} else {
prevSubCat.details.push(rest);
}
}
return acc;
}, []);
console.log(output);

Related

Is this the best approach to restructure an array and reorganize it based off a sub array's value

I have the below array which has a sub array of categories, I would like to output the array over and over but grouping the items into another array based on their related categories
testData2: any = [{
"id": 0,
"name": "XyZ",
"category": [ {
"title": "horse"
}, {
"title": "food"
}]
}, {
"id": 2,
"name": "something 2",
"category": [{
"title": "fishing"
}, {
"title": "horse"
}, {
"title": "food"
}]
},
{
"id": 3,
"name": "something 3",
"category": [{
"title": "horse"
}]
}, {
"id": 4,
"name": "something 4",
"category": [{
"title": "food"
}, {
"title": "beer"
}]
}, {
"id": 5,
"name": "something 4",
"category": [{
"title": "fishing"
}]
}
]
So far I have this which works, but i cant help wonder if there is some new JS magic which may be more perfomant to accomplish this ?
let newArray = [];
for (let x = 0; x < this.testData2.length; x++) {
let parent = this.testData2[x];
let child = parent.category;
for (let y = 0; y < child.length; y++) {
let cat = child[y];
let format = parent
newArray.push({ group_heading: cat.title, services: [format] })
}
}
let finalOutput = newArray.reduce((acc, curr) => {
const ndx = acc.findIndex((e: any) => e.group_heading === curr.group_heading);
if(ndx > -1){
acc[ndx].services.push(...curr.services)
} else{
acc.push(curr)
}
return acc;
}, [])
which outputs this as desired
[{
"group_heading": "horse",
"services": [{
"id": 0,
"name": "XyZ",
"category": [{
"title": "horse"
}, {
"title": "food"
}]
}, {
"id": 2,
"name": "something 2",
"category": [{
"title": "fishing"
}, {
"title": "horse"
}, {
"title": "food"
}]
}, {
"id": 3,
"name": "something 3",
"category": [{
"title": "horse"
}]
}]
}, {
"group_heading": "food",
"services": [{
"id": 0,
"name": "XyZ",
"category": [{
"title": "horse"
}, {
"title": "food"
}]
}, {
"id": 2,
"name": "something 2",
"category": [{
"title": "fishing"
}, {
"title": "horse"
}, {
"title": "food"
}]
}, {
"id": 4,
"name": "something 4",
"category": [{
"title": "food"
}, {
"title": "beer"
}]
}]
}, {
"group_heading": "fishing",
"services": [{
"id": 2,
"name": "something 2",
"category": [{
"title": "fishing"
}, {
"title": "horse"
}, {
"title": "food"
}]
}, {
"id": 5,
"name": "something 4",
"category": [{
"title": "fishing"
}]
}]
}, {
"group_heading": "beer",
"services": [{
"id": 4,
"name": "something 4",
"category": [{
"title": "food"
}, {
"title": "beer"
}]
}]
}]
I would probably do something like this:
// first collect services by category
const servicesByCategory = {}
for(const service of testData2){
for(const {title} of service.category){
if(!servicesByCategory[title]){
servicesByCategory[title] = []
}
servicesByCategory[title].push(data)
}
}
// whip it into whatever form you need
return Object.entries(servicesByCategory)
.map(([group_headings, services]) => ({group_headings, services}))

Loop Array of Object and connect them together by different keys

I'm trying to loop thru products array and find its description in description array. The product id and description parent represent the same product. If the description could be found, push the product with its description to the results array.
I don't really know how this loop should look like.
Products
let products = [
{
"id": "43",
"titel": "Phone",
"price": "211"
},{
"id": "76",
"titel": "Battery",
"price": "34"
},{
"id": "102",
"titel": "Pen",
"price": "45"
},{
"id": "127",
"titel": "Apple",
"price": "10"
}
]
Descriptions
let descriptions= [
{
"description": "Good battery",
"parent": "76"
},{
"description": "Sharp pen",
"parent": "102"
},
]
Expected output results
let results = [
{
"id": "76",
"titel": "Battery",
"price": "34"
"description": "Good battery",
"parent": "76"
},{
"id": "102",
"titel": "Pen",
"price": "45"
"description": "Sharp pen",
"parent": "102"
},
]
You can take advantage of Array.prototype.reduce which allows to transform an array to another type of Object of array.
combine with the Array.prototype.find to check if item with a given id exists in the descriptions Array
let products = [
{
"id": "43",
"titel": "Phone",
"price": "211"
},{
"id": "76",
"titel": "Battery",
"price": "34"
},{
"id": "102",
"titel": "Pen",
"price": "45"
},{
"id": "127",
"titel": "Apple",
"price": "10"
}
]
let descriptions= [
{
"description": "Good battery",
"parent": "76"
},{
"description": "Sharp pen",
"parent": "102"
},
]
const result = products.reduce((acc, item) => {
// Check if the description of the current product exists
let exist = descriptions.find(desc => {
return item.id === desc.parent;
});
if(exist) {
return acc.concat({...item, description: exist.description});
}
return acc;
}, []);
console.log(result);
If you add another array comments you can update the code instead of to concatenate the item in the accumulator directly you'll create another object which after finding the related comment in the comment array you'll add the comment key in that object with their comment.
Here is the code
let products = [
{
"id": "43",
"titel": "Phone",
"price": "211"
},{
"id": "76",
"titel": "Battery",
"price": "34"
},{
"id": "102",
"titel": "Pen",
"price": "45"
},{
"id": "127",
"titel": "Apple",
"price": "10"
}
]
let descriptions= [
{
"description": "Good battery",
"parent": "76"
},{
"description": "Sharp pen",
"parent": "102"
},
]
let comments = [
{
"comment": "Good battery comment",
"product": "76",
}, {
"comment": "Sharp pen comment",
"product": "102"
}
];
const result = products.reduce((acc, item) => {
// Check if the description of the current product exists
let productExists = descriptions.find(desc => {
return item.id === desc.parent;
});
let commentExists = comments.find(comment => {
return item.id === comment.product
});
let newItem = null;
if(productExists) {
newItem = {
...item,
description: productExists.description
};
}
if(commentExists) {
newItem.comment = commentExists.comment;
}
return newItem? acc.concat(newItem): acc;
}, []);
console.log(result);
You should iterate over the descriptions, then use there Array.find and merge them together into a new object, with Object.assign and push them to your results
let products = [
{
"id": "43",
"titel": "Phone",
"price": "211"
},{
"id": "76",
"titel": "Battery",
"price": "34"
},{
"id": "102",
"titel": "Pen",
"price": "45"
},{
"id": "127",
"titel": "Apple",
"price": "10"
}
];
let descriptions= [
{
"description": "Good battery",
"parent": "76"
},{
"description": "Sharp pen",
"parent": "102"
},
];
let results = [];
for (const desc of descriptions) {
const product = products.find(v => v.id === desc.parent);
if (!product) {
continue;
}
results.push(Object.assign({}, product, desc));
}
console.log(results);
const result = descriptions.map(descr => {
const product = products.find(prod => prod.id === descr.parent);
return {...product, ...descr}
})

how to compare the key in the array of object and modify it using javascript

I have two objects obj1 and obj2, how to loop through array key children and if code name matches, then add the name key in the object obj2, get the object.
How to loop through object of objects and match the key and push the key to get the new object in javascript
function newObject(obj1, obj2) {
var result = obj1.map(e => {
if ('children' in e)
e.children = e.children.map(child => {
if ('children' in child)
child.children = child.children.map(c => {
name: c.name
});
return child;
});
return e;
})
return result
}
var obj1 = [{
"id": 1,
"item": "node1",
"children": [{
"id": 2,
"code": "countries",
"title": "Country",
"children": [{
"cid": 12,
"code": "S1",
"name": "SG",
"children": [{
"id": 4,
"code": "C1",
"name": "City"
}]
},
{
"cid": 13,
"code": "S2",
"name": "TH"
}
]
},
{
"id": 2,
"code": "Groceries",
"title": "Grocery",
"children": [{
"cid": 11,
"code": "G1",
"name": "Fruits"
},
{
"cid": 10,
"code": "G2",
"name": "Vegetables"
}
]
}, {
"id": 3,
"code": "lists",
"title": "Option"
}
]
}];
var obj2 = [{
id: 1,
code: "G1",
status: "active"
},
{
id: 2,
code: "S2",
status: "inactive"
},
{
id: 3,
code: "C1",
status: "active"
}
];
console.log(this.newObject(obj1, obj2));
Expected Output
[
{id:1, name: "Fruits",code:"G1", status:"active"},
{id:2, name: "TH",code:"S2", status:"inactive"},
{id:3, name: "city",code:"C1", status:"active"}
]
you can check this code
I fixed your object and you can find your expectation in the console

How to search product name from category object using lodash or any JS library in VueJS

I using below object for search product from category object in vue.js.
I have try using that way but not working:
_.find(this.allCategories, _.flow(
_.property('product'),
_.partialRight(_.some, { product_name: "Shir" })
));
My object:
[{
"id": 2,
"name": "Shirt",
"product": [{
"id": 1,
"product_name": "Shirt 123",
"price": "150.00",
"image": "http://localhost/l9FRGvZDfb.jpeg"
}, {
"id": 4,
"product_name": "Shirt 456",
"price": "12.44",
"image": "http://localhost/A5rmGtOnW9.jpeg"
}]
}, {
"id": 2,
"name": "Froots",
"product": [{
"id": 1,
"product_name": "Apple 121",
"image": "http://localhost/Dfb.jpeg"
}, {
"id": 4,
"product_name": "Banana 121",
"price": "12.44",
"image": "http://localhost/tOnW9.jpeg"
}]
}]
Below snippet (in vanilla js) could help you
const categories = [
{
id: 2,
name: "Shirt",
product: [
{
id: 1,
product_name: "Shirt 123",
price: "150.00",
image: "http://localhost/l9FRGvZDfb.jpeg",
},
{
id: 4,
product_name: "Shirt 456",
price: "12.44",
image: "http://localhost/A5rmGtOnW9.jpeg",
},
],
},
{
id: 2,
name: "Froots",
product: [
{
id: 1,
product_name: "Apple 121",
image: "http://localhost/Dfb.jpeg",
},
{
id: 4,
product_name: "Banana 121",
price: "12.44",
image: "http://localhost/tOnW9.jpeg",
},
],
},
]
const term = "hirt"
const res = categories
.flatMap(({ product }) => product)
.filter(({ product_name }) =>
product_name.toLowerCase().includes(term.toLowerCase())
)
console.log(res)
Lodash version
const categories = [
{
id: 2,
name: "Shirt",
product: [
{
id: 1,
product_name: "Shirt 123",
price: "150.00",
image: "http://localhost/l9FRGvZDfb.jpeg",
},
{
id: 4,
product_name: "Shirt 456",
price: "12.44",
image: "http://localhost/A5rmGtOnW9.jpeg",
},
],
},
{
id: 2,
name: "Froots",
product: [
{
id: 1,
product_name: "Apple 121",
image: "http://localhost/Dfb.jpeg",
},
{
id: 4,
product_name: "Banana 121",
price: "12.44",
image: "http://localhost/tOnW9.jpeg",
},
],
},
]
const term = "hirt"
const res = _.chain(categories)
.flatMap("product")
.filter((product) =>
product.product_name.toLowerCase().includes(term.toLowerCase())
)
.value()
console.log(res)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>
My experience with Lodash is limited, but here's a fairly simple vanilla JS example that should accomplish what you're attempting to do. The comments should run through each step, but let me know if you would like me to expand on any of the code.
const data = [{
"id": 2,
"name": "Shirt",
"product": [{
"id": 1,
"product_name": "Shirt 123",
"price": "150.00",
"image": "http://localhost/l9FRGvZDfb.jpeg"
}, {
"id": 4,
"product_name": "Shirt 456",
"price": "12.44",
"image": "http://localhost/A5rmGtOnW9.jpeg"
}]
}, {
"id": 2,
"name": "Froots",
"product": [{
"id": 1,
"product_name": "Apple 121",
"image": "http://localhost/Dfb.jpeg"
}, {
"id": 4,
"product_name": "Banana 121",
"price": "12.44",
"image": "http://localhost/tOnW9.jpeg"
}]
}]
const input = 'Shir'
// Obtain a flattened array of all products
const products = data.map(item => item.product).flat()
// Filter the list of products by name that starts with the input
// Note that you can replace the `product.product_name.startsWith(input)` with any filter function to improve your search
const output = products.filter(product => product.product_name.startsWith(input))
console.log(output)
Edit:
Per VR702's comment...
Thanks for answer :). I have implemented this code but it's return only product object but I want with category object
...we need to print out the categories that the product came from rather than the product itself. Note that I needed to modify the data to make the category IDs unique (as it seemed that may have been an error). If it's not an error, the name property can be used in its place.
const categories = [{
"id": 2,
"name": "Shirt",
"product": [{
"id": 1,
"product_name": "Shirt 123",
"price": "150.00",
"image": "http://localhost/l9FRGvZDfb.jpeg"
}, {
"id": 4,
"product_name": "Shirt 456",
"price": "12.44",
"image": "http://localhost/A5rmGtOnW9.jpeg"
}]
}, {
"id": 3,
"name": "Froots",
"product": [{
"id": 1,
"product_name": "Apple 121",
"image": "http://localhost/Dfb.jpeg"
}, {
"id": 4,
"product_name": "Banana 121",
"price": "12.44",
"image": "http://localhost/tOnW9.jpeg"
}]
}]
const input = 'Shir'
// Obtain a flattened array of all products
// Include the id of the category it came from as well
const products = []
categories.forEach(category => {
category.product.forEach(product => {
products.push({
category: category.id,
...product
})
})
})
// Filter the list of products by name that starts with the input
// Note that you can replace the `product.product_name.startsWith(input)` with any filter function to improve your search
// Then return the category that that product came from
// Finally remove any duplicates using [...new Set()]
const selectedCategories = [...new Set(
products
.filter(product => product.product_name.startsWith(input))
.map(product => product.category)
)]
// Return the categories we found by searching through our initial list of categories
const output = categories.filter(category => selectedCategories.includes(category.id))
console.log(output)
you not need library !!!
You can find the desired string with js.
var product = [
{
"id":1,
"product_name":"Shirt 123",
"price":"150.00",
"image":"http://localhost/l9FRGvZDfb.jpeg"
},
{
"id":4,
"product_name":"Shirt 456",
"price":"12.44",
"image":"http://localhost/A5rmGtOnW9.jpeg"
}
]
var searchField = "Shirt 456";
for (var i=0 ; i < product.length ; i++)
{
if (product[i].product_name == searchField) {
console.log(product[i])
}
}

What is the best way to join two json files in angular?

I have a json file which contains the list of products.
[{"id":76,
"name":"A",
"description":"abc",
"price":199,
"imageUrl":"image.jpg",
"productCategory":[{
"categoryId":5,
"category":null
},{
"categoryId":6,
"category":null
}
]}
I then have a second json file with a list of categories which look like so:
[{"id":5,"name":"red"},
{"id":6,"name”:"blue"}]
What is the best way to join the categories of this two json files in Angular?
This is what I aim to achieve:
[{"id":76,
"name":"A",
"description":"abc",
"price":199,
"imageUrl":"image.jpg",
"productCategory":[{
"categoryId":5,
"category":red
},{
"categoryId":6,
"category":blue
}
]}
You can use filter function for your requirement as below
let products = [{
"id": 76,
"name": "A",
"description": "abc",
"price": 199,
"imageUrl": "image.jpg",
"productCategory": [{
"categoryId": 2,
"category": null
}, {
"categoryId": 1,
"category": null
}]
}, {
"id": 77,
"name": "B",
"description": "abcd",
"price": 1997,
"imageUrl": "image.jpg",
"productCategory": [{
"categoryId": 5,
"category": null
}, {
"categoryId": 6,
"category": null
}]
},
{
"id": 78,
"name": "C",
"description": "abcde",
"price": 1993,
"imageUrl": "image.jpg",
"productCategory": [{
"categoryId": 4,
"category": null
}, {
"categoryId": 6,
"category": null
}]
}];
let category = [{ "id": 5, "name": "red" }, { "id": 6, "name": "blue" }]
let result = products.filter(p => {
var exist = p.productCategory.filter(pc => category.find(c => c.id == pc.categoryId))[0];
return exist;
});
console.log(result);
let products = [{
"id": 76,
"name": "A",
"description": "abc",
"price": 199,
"imageUrl": "image.jpg",
"productCategory": [{
"categoryId": 2,
"category": null
}, {
"categoryId": 1,
"category": null
}]
}, {
"id": 77,
"name": "B",
"description": "abcd",
"price": 1997,
"imageUrl": "image.jpg",
"productCategory": [{
"categoryId": 5,
"category": null
}, {
"categoryId": 6,
"category": null
}]
},
{
"id": 78,
"name": "C",
"description": "abcde",
"price": 1993,
"imageUrl": "image.jpg",
"productCategory": [{
"categoryId": 4,
"category": null
}, {
"categoryId": 6,
"category": null
}]
}];
let category = [{ "id": 5, "name": "red" }, { "id": 6, "name": "blue" }]
let result = products.filter(p => {
var exist = p.productCategory.filter(pc => category.find(c => c.id == pc.categoryId))[0];
return exist;
});
console.log(result);
I make a stackblitz that use a service to retreive the data. Yes, the way is using switchMap and map. SwitchMap receive an array and must return an observable. with map, we transform the data received and return the data transformed
this.dataService.getCategories().pipe(
//first get the categories, the categories is in the
//variable cats
switchMap((cats:any[])=>{
return this.dataService.getProducts().pipe(map((res:any[])=>{
res.forEach(p=>{ //with each product
p.productCategory.forEach(c=>{ //with each productCategory in product
//equals a propertie "category" to the propertie "name" of the cats
c.category=cats.find(x=>x.id==c.categoryId).name
})
})
return res
}))
})).subscribe(res=>{
console.log(res)
})
If only has an unique product we can make
this.dataService.getCategories().pipe(
switchMap((cats:any[])=>{
return this.dataService.getUniqProduct(2).pipe(map((res:any)=>{
res.productCategory.forEach(c=>{
c.category=cats.find(x=>x.id==c.categoryId).name
})
return res
}))
})).subscribe(res=>{
console.log(res)
})
We can improve our dataService "cached" the categories
getCategories() {
if (this.categories)
return of(this.categories);
return http.get(......).pipe(tap(res=>{
this.categories=res;
}))
}
NOTE:In the stackbit I simulate the call to an http.get(...) using "of"

Categories