How to get size of array embedded array mongoose? - javascript

I have a category collection and I want to get category by id with some options. This is collection's structure in database.
[
{
"_id": "5d67296bf35b984e74486924",
"name": "Dinrk",
"images": [],
"recipes": [
{
"name": "Coffee",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"components": [
{
"name": "Dink 1",
"quantity": "1"
},
{
"name": "Dink 2",
"quantity": "1"
},
{
"name": "Dink 2",
"quantity": "1"
}
],
"cook_steps": [
{
"des": "This is description",
"pictures": []
},
{
"des": "This is description",
"pictures": []
}
]
},
{
"name": "Coffee",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"components": [
{
"name": "Dink 1",
"quantity": "1"
},
{
"name": "Dink 2",
"quantity": "1"
}
],
"cook_steps": [
{
"des": "This is description",
"pictures": []
},
{
"des": "This is description",
"pictures": []
}
]
}
]
},
{
"_id": "5d67296bf35b984e74486435555",
"name": "Cake",
"images": [],
"recipes": [
{
"name": "Cake",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"components": [
{
"name": "Cake 1",
"quantity": "1"
},
{
"name": "Cake 2",
"quantity": "1"
},
{
"name": "Cake 2",
"quantity": "1"
}
],
"cook_steps": [
{
"des": "This is description",
"pictures": []
},
{
"des": "This is description",
"pictures": []
}
]
},
{
"name": "Coffee",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"components": [
{
"name": "Cake 1",
"quantity": "1"
}
],
"cook_steps": [
{
"des": "This is description",
"pictures": []
},
{
"des": "This is description",
"pictures": []
}
]
}
]
}
]
This is my code to try categoryId = "5d67296bf35b984e74486924"
Category.aggregate([
{
$match: {'_id': categoryId}
},
{
$unwind: '$recipes'
},
{
$project: {
'total_components': {'$size': '$recipes.components'},
'total_cook_steps': {'$size': '$recipes.cook_steps'}
}
}
]).then(function(data) {
}, function(err) {
})
And expected result is
{
"_id": "5d67296bf35b984e74486924",
"name": "Dinrk",
"images": [],
"recipes": [
{
"name": "Coffee",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"total_components": 3,
"total_cook_steps": 2
},
{
"name": "Coffee",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"total_components": 2,
"total_cook_steps": 2
}
]
}
But when I run above my code, result is [].
If you understand my problem, please help me. I have search a lot, but not found solution. So I want to ask everyone. Thankyou so much.

Your query is not giving you the desired result since Mongoose does not auto-cast the 24 char hex string to ObjectId in its aggregate pipeline since $project and $group can change the schema in surprising ways that it becomes hard to infer what should be an ObjectId.
You need to manually convert the categoryId
string to ObjectId using the mongoose.Types.ObjectId method.
Compute the new fields within a $map operator instead of $unwind as this allows you an aggregate operation with fewer pipeline steps
Category.aggregate([
{ '$match': { '_id': mongoose.Types.ObjectId(categoryId) } },
{ '$addFields': {
'recipes': {
'$map': {
'input': '$recipes',
'in': {
'name': '$$this.name',
'time': '$$this.time',
'img': '$$this.img',
'des': '$$this.des',
'serving': '$$this.serving',
'total_components': { '$size': '$$this.components' },
'total_cook_steps': { '$size': '$$this.cook_steps' }
}
}
}
} }
]).then(function(data) {
}, function(err) {
})

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

How to combine nested duplicate dictionary values within an array

I have a following data structure, array of dictionaries
[
{
"id": 1,
"name": "Some Name 1",
"topics": [
{
"id": 3,
"name": "Topic Name",
"topics": [],
},
{
"id": 3,
"name": "Topic Name",
"topics": [],
}
]
},
{
"id": 2,
"name": "Some Name 2",
"topics": [
{
"id": 3,
"name": "Topic Name",
"topics": [],
},
{
"id": 3,
"name": "Topic Name",
"topics": [],
}
]
},
[
{
"id": 1,
"name": "Some Name 2",
"topics": [
{
"id": 1,
"name": "Topic Name 1",
"topics": [],
},
{
"id": 2,
"name": "Topic Name 2",
"topics": [],
}
]
},
{
"id": 2,
"name": "Some Name 1",
"topics": [
{
"id": 3,
"name": "Topic Name",
"topics": [],
},
{
"id": 3,
"name": "Topic Name",
"topics": [],
}
]
},
I want to essentially be able to combine the arrays within each dictionary if I have the same id, so for each dictionary i will have higher level dictionary items being the same and essentially combine those together to avoid duplication. Preferably i would like it to be recursively being that in case the topics within topics are also duplicate (you can assume the order is always respected).
I would appreciate if you could share a solution to this

Javascript: Efficient way Nested Array mapping & filtering to get the desired results

I have tried the below solution for question2 to get result2. What would be a more efficient way to get both the result (result1, result2 & result3) with the same code?
question1 = [
{
"groupName": "Group 1",
"id": "group1",
"options": [
{
"name": "Cat1",
"selected": true
},
{
"name": "Cat2",
"selected": true
},
{
"name": "Cat3",
"selected": false
},
{
"name": "Cat4",
"selected": false
}
]
},
{
"groupName": "Group 2",
"id": "Brand",
"options": [
{
"name": "brand1",
"selected": false
},
{
"name": "brand2",
"selected": true
},
{
"name": "brand3",
"selected": false
}
]
},
{
"groupName": "Group 3",
"id": "Price",
"options": [
{
"name": "$0 - $9",
"selected": false
},
{
"name": "$9 - $19",
"selected": false
},
{
"name": "$20 - $29",
"selected": false
}
],
"range": {
"min": 5,
"max": 20
}
}
]
result1 = [{
"groupName": "Group 1",
"id": "group1",
"name": "Cat1",
},
{
"groupName": "Group 1",
"id": "group1",
"name": "Cat2",
},
{
"groupName": "Group 2",
"id": "Brand",
"name": "brand2",
},
{
"groupName": "Group 3",
"id": "Price",
"min": 5,
"max": 20
}
]
I have a solution for question2, although I know it's not very efficient, so what could be a better solution?
question2 = [
{
"groupName": "Group 1",
"id": "group1",
"options": [
{
"name": "Cat1",
"selected": true
},
{
"name": "Cat2",
"selected": false
},
{
"name": "Cat3",
"selected": false
},
{
"name": "Cat4",
"selected": false
}
]
},
{
"groupName": "Group 2",
"id": "Brand",
"options": [
{
"name": "brand1",
"selected": false
},
{
"name": "brand2",
"selected": true
},
{
"name": "brand3",
"selected": false
}
]
},
{
"groupName": "Group 3",
"id": "Price",
"options": [
{
"name": "$0 - $9",
"selected": true
},
{
"name": "$9 - $19",
"selected": false
},
{
"name": "$20 - $29",
"selected": false
}
],
"range": {
"min": null,
"max": null
}
}
]
const selected2 = question2.map(group => group.options.filter(option => option.selected).map(option => ({groupName: group.groupName, id: group.id, name: option.name}))).flat(1)
console.log(selected2)
For question3, group 1, none of the options is selected and correspondingly I should get result3. So, only if any of the options are selected or the price has a range (either min/max/both min&max), it is displayed in the result.
question3 = [
{
"groupName": "Group 1",
"id": "group1",
"options": [
{
"name": "Cat1",
"selected": false
},
{
"name": "Cat2",
"selected": false
},
{
"name": "Cat3",
"selected": false
},
{
"name": "Cat4",
"selected": false
}
]
},
{
"groupName": "Group 2",
"id": "Brand",
"options": [
{
"name": "brand1",
"selected": false
},
{
"name": "brand2",
"selected": true
},
{
"name": "brand3",
"selected": false
}
]
},
{
"groupName": "Group 3",
"id": "Price",
"options": [
{
"name": "$0 - $9",
"selected": false
},
{
"name": "$9 - $19",
"selected": false
},
{
"name": "$20 - $29",
"selected": false
}
],
"range": {
"min": 5,
"max": 20
}
}
]
result3 = [
{
"groupName": "Group 2",
"id": "Brand",
"name": "brand2",
},
{
"groupName": "Group 3",
"id": "Price",
"min": 5,
"max": 20
}
]
I want to use reduce to get these above (result1, result2 & result3) solutions. Could someone help me with this?
Edited to check scenario where two are selected in the same group, solution resulting in result1.
When multiple selected trues found in the accumulator object I'm adding a key where keyname is a combination of groupName and selected name. If no selected true but has max or min values the key name will be just groupName. I take Object.values() of the final object to get the array of values.
const question1 = [{"groupName": "Group 1","id": "group1","options": [{"name": "Cat1","selected": true},{"name": "Cat2","selected": true},{"name": "Cat3","selected": false},{"name": "Cat4","selected": false}]},{"groupName": "Group 2","id": "Brand","options": [{"name": "brand1","selected": false},{"name": "brand2","selected": true},{"name": "brand3","selected": false}]},{"groupName": "Group 3","id": "Price","options": [{"name": "$0 - $9","selected": false},{"name": "$9 - $19","selected": false},{"name": "$20 - $29","selected": false}],"range": {"min": 5,"max": 20}}]
const question2 = [{"groupName": "Group 1","id": "group1","options":[{"name":"Cat1","selected": true},{"name": "Cat2","selected": false},{"name":"Cat3","selected": false},{"name": "Cat4","selected": false}]},{"groupName": "Group 2","id": "Brand","options": [{"name":"brand1","selected":false},{"name": "brand2","selected": true},{"name": "brand3","selected": false}]},{"groupName": "Group 3","id": "Price","options": [{"name": "$0 -$9","selected": true},{"name": "$9 - $19","selected": false},{"name": "$20 -$29","selected": false}],"range": {"min": null,"max": null}}]
const question3 = [{"groupName": "Group 1","id": "group1","options": [{"name": "Cat1","selected": false},{"name": "Cat2","selected": false},{"name": "Cat3","selected": false},{"name": "Cat4","selected": false}]},{"groupName": "Group 2","id": "Brand","options": [{"name": "brand1","selected": false},{"name": "brand2","selected": true},{"name": "brand3","selected": false}]},{"groupName": "Group 3","id": "Price","options": [{"name": "$0 - $9","selected": false},{"name": "$9 - $19","selected": false},{"name": "$20 - $29","selected": false}],"range": {"min": 5,"max": 20}}]
const formatter = (arr) => {
return Object.values(arr.reduce((acc,curr) => {
const hasMax = curr.range && curr.range.max;
const hasMin = curr.range && curr.range.max
const filtered = curr['options'].filter((option) => option.selected);
if (filtered.length) {
filtered.forEach((el) => {
acc[curr.groupName+el.name] = {id: curr.id,groupName: curr.groupName,name: el.name}
})
}
else if (hasMin || hasMax){
acc[curr.groupName] = {id: curr.id,groupName: curr.groupName}
if(hasMax) acc[curr.groupName]['max'] = curr.range.max
if(hasMin) acc[curr.groupName]['min'] = curr.range.min
}
return acc;
},{}))
}
const result1 = formatter(question1)
const result2 = formatter(question2)
const result3 = formatter(question3)
console.log(result1)
console.log(result2)
console.log(result3)
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to determine intersect objects in json array with javascript?

I have json data like this:
[{
"Id": 1,
"Name": "Test 1",
"Time": {
"start": "22:00",
"end": "22:30",
"duration": 30
}
}, {
"Id": 2,
"Name": "Test 2",
"Time": {
"start": "22:05",
"end": "22:35",
"duration": 30
}
}, {
"Id": 3,
"Name": "Test 3",
"Time": {
"start": "23:25",
"end": "23:40",
"duration": 15
}
},{
"Id": 4,
"Name": "Test 4",
"Time": {
"start": "22:15",
"end": "22:50",
"duration": 15
}
}
]
And i want get result like this, determine which intersect by Time:
[
["Test 1", "Test 2", "Test 4"],
["Test 3"]
]
I have solution, but it's too complicated and i use 4 loops inside each other
If there is an easier solution, please suggest, thanks!
Try aggregation, I am not sure this will work exact as per your requirement,
$addFields add new field startHour, that splits string hour and minute from string, using $split, and $arrayElemAt return first element means hour
$group by startHour and make array of Name
db.collection.aggregate([
{
$addFields: {
startHour: {
$arrayElemAt: [{ $split: ["$Time.start", ":"] }, 0]
}
}
},
{
$group: {
_id: "$startHour",
Name: { $push: "$Name" }
}
}
])
Playground
Result:
[
{
"Name": ["Test 1","Test 2","Test 4"],
"_id": "22"
},
{
"Name": ["Test 3"],
"_id": "23"
}
]

Normalising JSON data

{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
}
}
]
}
This is JSON for an article
{
result: "123",
entities: {
"articles": {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: [ "324" ]
}
},
"users": {
"1": { "id": "1", "name": "Paul" },
"2": { "id": "2", "name": "Nicole" }
},
"comments": {
"324": { id: "324", "commenter": "2" }
}
}
}
Im currently looking through redux tutorials, they give a helper which normalise JSON data, does anyone know what type of normalisation this is?

Categories