How to restructure an array base on an value inside the original array in node js - node.js

I am trying to restructure an array. Instead of variant_id at the top, i would like to make collection_id to the top. if the collection_id is the same they should be in the same array. I try using foreach but seems didn't work.
[
{
variant_id: '39264031277264',
collection: [
{
collection_id: '260687954128',
value: true
}
]
},
{
variant_id: '39264022134992',
collection: [
{
collection_id: '260687954128',
value: true
}
]
},
{
variant_id: '40460795642064',
collection: [
{
collection_id: '277033681104',
value: true
}
]
}
]
This is the expected result
[
{
"collection_id":"260687954128",
"variant_id":[
"39264031277264",
"39264022134992"
]
},
{
"collection_id":"277033681104",
"variant_id":[
"40460795642064"
]
}
]

You can define a simple mapping function that collects all collection_ids and keeps track of the respective variant_ids:
const data = [{
variant_id: '39264031277264',
collection: [{collection_id: '260687954128', value: true}]
}, {
variant_id: '39264022134992',
collection: [{collection_id: '260687954128', value: true}]
}, {variant_id: '40460795642064', collection: [{collection_id: '277033681104', value: true}]}
];
function mapData(input) {
const collections = {};
for (const variant of input) {
for (const collection of variant.collection) {
collections[collection.collection_id] = collections[collection.collection_id] ?? [];
collections[collection.collection_id].push(variant.variant_id);
}
}
return Object
.entries(collections)
.map(([key, value]) => ({collection_id: key, variant_id: value}));
}
console.log(mapData(data));

The below solution may be one possible way to achieve the desired objective.
It uses the following JavaScript concepts/features/ideas:
Object.values
Array.reduce()
De-structuring
... spread operator
?. optional chaining operator
?? nullish coalescing operator
Implicit return
Code Snippet
const reGroupUsingCollectionId = arr => (
Object.values( // extract only the 'values' of resulting object
arr.reduce( // use .reduce() to iterate the array
(acc, itm) => ( // 'acc' is the accumulator & 'itm' is current element
itm.collection.reduce( // use .reduce() to iterate over 'collection' array
(fin, {collection_id}) => ({ // 'fin' is the inner-reduce's accumulator
...fin, // collection_id is de-structured from iterator
[collection_id]: {
collection_id,
variant_id: [ // populate the variant_id array
...( //
fin[collection_id] // use existing variant_id array if collection_id
?.variant_id // already added to accumulator (fin)
?? [] // nullish coalescing as empty
),
itm.variant_id // append itm.variant_id to variant_id array
]
}
}),
{...acc} // initialize 'fin' to be 'acc'
)
),
{} // initialize 'acc' to be empty object
)
)
);
const data = [{
variant_id: '39264031277264',
collection: [{
collection_id: '260687954128',
value: true
}]
}, {
variant_id: '39264022134992',
collection: [{
collection_id: '260687954128',
value: true
}]
}, {
variant_id: '40460795642064',
collection: [{
collection_id: '277033681104',
value: true
}]
}
];
console.log(reGroupUsingCollectionId(data));
Explanation
Inline comments provide detailed description of each significant step.

Related

mongoose return an object from an array based on condition

I want to get a specific object from an array in a record from MongoDB. I want to get the entire collection so I cannot apply my condition in find but inside the record, I have an array I want to return an object from that array based on a condition.
await model
.find({})
.select("translations")
.then((data: any) => {
return res.status(200).json({
data
});
});
Now here translations has many objects
[
{id:'', name: 'EN'},
{id:'', name: 'AR'},
{id:'', name: 'FR'}
]
How can I only return the translation which has the name 'EN'
db.collection.aggregate(
[
{
'$project': {
'translation': {
'$filter': {
'input': '$translation',
'as': 't',
'cond': {
'$eq': [
'$$t.name', 'EN'
]
}
}
}
}
}
]
)

Update multiple objects in nested array

Question: Is it possible to update multiple objects in a nested array based on another field in the objects, using a single Mongoose method?
More specifically, I'm trying to update subscribed in each object of the Contact.groups array where the object's name value is included in groupNames. Solution 1 works, but it seems messy and inefficient to use both findOne() and save(). Solution 2 is close to working with just findOneAndUpdate(), but only the first eligible object in Contact.groups is updated. Am I able to update all the eligible objects using just findOneAndUpdate()?
Contact schema (trimmed down to relevant info):
{
phone: { type: String, unique: true },
groups: [
{
name: { type: String },
subscribed: { type: Boolean }
}
]
}
Variables I have at this point:
const phoneToUpdate = '1234567890' // Contact.phone to find
const groupNames = [ 'A', 'B', 'C' ] // Contacts.groups <obj>.name must be one of these
const subStatus = false // Contacts.groups <obj>.subscribed new value
Solution 1 (seems inefficient and messy):
Contact
.findOne({ phone: phoneToUpdate })
.then(contact => {
contact.groups
.filter(g => groupNames.includes(g.name))
.forEach(g => g.subscribed = subStatus)
contact
.save()
.then(c => console.log(c))
.catch(e => console.log(e))
})
.catch(e => console.log(e))
Solution 2 (only updates the first matching object):
Contact
.findOneAndUpdate(
{ phone: phoneToUpdate, 'groups.name': { $in: groupNames } },
{ $set: { 'groups.$.subscribed': subStatus } },
{ new: true }
)
.then(c => console.log(c))
.catch(error => console.log(error))
// Example Contact after the findOneAndUpdate
{
phone: '1234567890',
groups: [
{ name: 'A', subscribed: false },
{ name: 'B', subscribed: true } // Should also be false
]
}
You can not use $ operator since he will act as a placeholder only for the first match.
The positional $ operator acts as a placeholder for the first match of
the update query document.
What you can use is arrayFilters operator. You can modify your query like this:
Contact.findOneAndUpdate({
"phone": phoneToUpdate
},
{
"$set": {
"groups.$[elem].subscribed": subStatus
}
},
{
"arrayFilters": [
{
"elem.name": {
"$in": groupNames
}
}
]
})
Here is a working example: https://mongoplayground.net/p/sBT-aC4zW93

How to pass an optional argument in Mongoose/MongoDb

I have the following query:
Documents.find({
$and: [
{
user_id: {$nin:
myUserId
}
},
{ date: { $gte: dateMax, $lt: dateMin } },
{documentTags: {$all: tags}}
],
})
What I'm trying to do is make the documentTags portion of the query optional. I have tried building the query as follows:
let tags = " ";
if (req.body.tags) {
tags = {videoTags: {$all: req.body.tags}};
}
let query = {
$and: [
{
user_id: {$nin:
myUserId
}
},
{ date: { $gte: dateMax, $lt: dateMin } },
tags
],
}
and then Document.find(query). The problem is no matter how I modify tags (whether undefined, as whitespace, or otherwise) I get various errors like $or/$and/$nor entries need to be full objects and TypeError: Cannot read property 'hasOwnProperty' of undefined.
Is there a way to build an optional requirement into the query?
I tried the option below and the query is just returning everything that matches the other fields. For some reason it isn't filtering by tags. I did a console.log(queryArr) and console.log(query) get the following respectively:
[
{ user_id: { '$nin': [Array] } },
{
date: {
'$gte': 1985-01-01T00:00:00.000Z,
'$lt': 2020-01-01T00:00:00.000Z
}
},
push: { documentTags: { '$all': [Array] } }
]
console.log(query)
{
'$and': [
{ user_id: [Object] },
{ date: [Object] },
push: { documentTags: [Object] }
]
}
You are almost there. Instead you could construct the object outside the query and just put the constructed query in $and when done..
let queryArr = [
{
user_id: {$nin: myUserId}
},
{ date: { $gte: dateMax, $lt: dateMin } }
];
if (req.body.tags) {
queryArr.push({videoTags: {$all: req.body.tags}});
}
let query = {
$and: queryArr
}
Now you can control the query by just pushing object into the query Array itself.
I figured out why it wasn't working. Basically, when you do myVar.push it creates a key-value pair such as [1,2,3,push:value]. This would work if you needed to append a k-v pair in that format, but you'll have difficulty using it in a query like mine. The right way for me turned out to be to use concact which appends the array with just the value that you set, rather than a k-v pair.
if (req.body.tags){
queryArgs = queryArgs.concat({documentTags: {$all: tags}});
}
let query = {
$and: queryArgs
}

Save array values based on ObjectId using mongoose

I try to save each document in an array as ObjectId, like that:
{
materials: {
active: "Steel",
description: "List of materials",
text: "Materials",
value: ["5c44ea8163bfea185e5e2dfb", "5c44ea8163bfea185e5e2dfc"]
}
}
I used an array of promises to save asynchronous each value and save the callback _id:
const reference = {
materials: {
...project.materials,
value: await Promise.all(project.materials.value.map(
async (value) => {
const { _id } = await Material.findOneAndUpdate({ name: value.name }, value, { upsert: true, new: true, setDefaultsOnInsert: true }).exec();
return mongoose.Types.ObjectId(_id);
}
))
},
...
}
There is another more simple way ?

Inline array store data in ExtJS4?

I'm unable to load inline array data into a store. In particular, this fails.
Can someone explain why? I even tried adding a memory proxy with an array reader and still no dice.
Ext.define('MyApp.store.ComboboxState', {
extend: 'Ext.data.Store',
constructor: function(cfg) {
var me = this;
cfg = cfg || {};
me.callParent([Ext.apply({
autoLoad: true,
storeId: 'ComboboxState',
data: [
[
'AL',
'Alabama'
]
]
,fields: [
{
name: 'state'
},
{
name: 'name'
}
]
}, cfg)]);
}
});
Still doesn't work with this memory proxy/array reader:
proxy: {
type: 'memory',
reader: {
type: 'array'
}
}
Just extend from ArrayStore, like this:
Ext.define('MyApp.store.ComboboxState', {
extend: 'Ext.data.ArrayStore',
constructor: function(cfg) {
var me = this;
cfg = cfg || {};
me.callParent([Ext.apply({
autoLoad: true,
storeId: 'ComboboxState',
data: [
[
'AL',
'Alabama'
]
]
,fields: ['state', 'name' ]
}, cfg)]);
}
});
JsFiddle to try: http://jsfiddle.net/voidmain/hKwbJ/

Resources