This is a question based on MongoDb - remove all fields that are null. The referred post only gives solution that removes null fields at the top level. However, how can I remove the null fields that embedded?
Please note that I have no idea of the possible names of the null fields and their depth, so I think we have to iterate over each field of each document.
This is an example:
{
"id": 14770467,
"f1": "a",
"f2": null,
"f3": [
{
"id": 76946819,
"f4": null
}
]
}
I'm expecting something like this:
{
"id": 14770467,
"f1": "a",
"f3": [
{
"id": 76946819
}
]
}
Thanks.
try this
const remove = (data) => {
for (let key in data) {
const val = data[key];
if (val == null) {
delete data[key];
} else if (Array.isArray(val)) {
val.forEach((v) => {
remove(v);
});
}
}
return data;
}
db.getCollection('Collection').find({}).forEach((data) => {
data = remove(data);
db.getCollection('OtherCollection').insert(data);
//db.getCollection('Collection').save(data); // update same record
print(data);
})
Above was not working for me. But was inspiration to seek for more.
This worked (with MongoDB shell version v4.0.5):
const remove= (obj) => {
Object.keys(obj).forEach(key => {
if (obj[key] && typeof obj[key] === 'object') removeEmpty(obj[key]);
else if (obj[key] == null) delete obj[key];
});
};
db.getCollection('Collection').find({}).forEach((data) => {
remove(data);
db.getCollection('OtherCollection').insert(data);
})
Related
I'm trying to create an API to validate a promocode. I have minimal experience with mongo and the backend in general so I'm a bit confused in what is the best approach to do what I'm trying to accomplish.
I have this PromoCode form in the client. When a user types a promocode I would like for my backend to
verify if the code exists in one of the docs.
if it exists then return that code, the value for that code and the couponId
if the code doesn't exist then return an error.
My db is structured like this. The user will type one of those codes inside the codes: []
{
"_id": {
"$oid": "603f7a3b52e0233dd23bef79"
},
"couponId": "rate50",
"value": 50,
"codes": ["K3D01XJ50", "2PACYFN50", "COKRHEQ50"]
},
{
"_id": {
"$oid": "603f799d52e0233dd23bef78"
},
"couponId": "rate100",
"value": 100,
"codes": ["rdJ2ZMF100", "GKAAYLP100", "B9QZILN100"]
}
My route is structure like this:
router.post('/promoCode', (req, res, next) => {
const { promoCode } = req.body;
console.log('this is the req.body.promoCode on /promoCode', promoCode)
if (!promoCode) {
throw new Error('A promoCode needs to be passed')
}
promoCodesModel
.validatePromoCode(req.body.promoCode)
.then((response) => {
console.log('response inside /promoCode', response)
res.status(200).json({ data: response })
})
.catch((error) => {
res.status(400).json({ result: 'nok', error: error })
})
})
The validatePromoCode function is the following:
const validatePromoCode = async (code) => {
try {
let promoCode = await PromoCodesModel.find(
{"codes": code},
{_id: 0, codes: { $elemMatch: { $eq: code }} })
console.log('This is the promocode', promoCode)
return promoCode
} catch (err) {
throw new Error (err.stack)
}
}
All this seems to sort of work since I get the following response when the code is typed correctly
{
"data": [
{
"codes": [
"COKRHEQ50"
]
}
]
}
when typed incorrectly I get
{
"data": []
}
What I would like to get back is. (How can I accomplish this ?). Thanks
// when typed correctly
{
"data": { value: 50, couponId: "rate50", code: "COKRHEQ50" }
}
// when typed incorrectly
{
"error": "this is not valid code"
}
TL;DR: I would like to return a formatted query with specific values from a mongo query or an error object if that value does not exist on the document object.
Ok just figured it out
To be able to get the this responsed (what I wanted):
{
"data": [
{
"codes": [
"K3D01XJ50"
],
"couponId": "rate50",
"value": 50
}
]
}
I ended up having to do this on validatePromoCode
onst validatePromoCode = async (code) => {
try {
let promoCode = await PromoCodesModel.find(
{ codes: code },
{ _id: 0, codes: { $elemMatch: { $eq: code } }, couponId: 1, value: 1 },
)
return promoCode
} catch (err) {
throw new Error(err.stack)
}
}
But is there a better way on doing this ? Thanks
I have to find all documents in collection, also those nested, by one field that all of them have (common_field). In xpath it would be something like this: //*[#common_field='value']. How to do something like this in mongo?
{
"_id": ObjectId(
"5e3800700045c500cecffb00"
),
"common_field": "100281",
"other_field": "other",
"A": {
"common_field": "313000000",
"a_field": "a"
},
"B": {
"common_field": "213125",
"b_field": "bb",
"B": {
"common_field": "543534",
"b_field": "b"
},
"C": {
"common_field": "312312",
"c_field": "c"
}
}
}
I don't think you can achieve it natively with the Aggregation framework, you have to write a recursive function. This one should work:
var CheckField = function (doc, fieldName, value) {
var ret = false;
Object.keys(doc).forEach(function (key) {
if (key == fieldName && doc[key] == value) {
ret = true;
} else {
if (typeof doc[key] == "object")
ret = ret || CheckField(doc[key], fieldName, value);
}
});
return ret;
};
Then use the function like this:
var ObjIds = []
db.col.find({}).forEach(function (myDoc) {
if (CheckField(myDoc, "common_field", "313000000"))
ObjIds.push(myDoc._id);
})
Either print documents directly of use returned array as query filter:
db.col.find({ _id: { $in: ObjIds } })
So I am trying to add a key to a returned post. But I can't seem to get lean() to work. How can I manipulate the returned post after save?
I was thinking maybe I need to add lean to my findById like this Post.findById(req.params.id).lean().then(). But that didn't work, plus that only makes the first initial post mutable. It will say
post.save is not a function
if I do it like Post.findById(req.params.id).lean().then() as well
I want to only return the object about to be sent back to the client, I do not want they key saved in the actual document.
Post.findById(req.params.id)
.then(post => {
if (
post.likes.filter(like => like.user.toString() === req.user.id)
.length === 0
) {
return res
.status(400)
.json({ notliked: "You have not yet liked this post" });
}
// Get remove index
const removeIndex = post.likes
.map(item => item.user.toString())
.indexOf(req.user.id);
// Splice out of array
post.likes.splice(removeIndex, 1);
// Save
post.save().then(post => {
post["liked"] = false; <-------
res.json(post);
});
})
edit
Post.findById(req.params.id)
.lean()
.then(post => {
if (
post.likes.filter(like => like.user.toString() === req.user.id)
.length === 0
) {
return res
.status(400)
.json({ notliked: "You have not yet liked this post" });
}
// Get remove index
const removeIndex = post.likes
.map(item => item.user.toString())
.indexOf(req.user.id);
// Splice out of array
post.likes.splice(removeIndex, 1);
post["liked"] = false;
res.json(post);
// Save
post.save();
})
gives error
post.save is not a function
You can simply do this by searching for the req.user.id inside the indexOf likes array
Post.findOne({ _id: req.params.id }).lean().then((post) => {
if (post.likes.indexOf(req.user.id) !== -1) {
post.isLiked = true
}
post.isLiked = false
res.json(post)
})
Far better with the aggregation
Post.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(req.user.id) }},
{ "$addFields": {
"isLiked": { "$in": [mongoose.Types.ObjectId(req.user.id), "$likes"] }
}}
])
EDIT :- If you want to update document then use update query
Post.findOneAndUpdate(
{ _id: req.params.id },
{ $pull: { likes: { user: req.user.id } }},
{ new: true }
).then((post) => {
res.json(post)
})
Post Schema for likes
...
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: "users"
}
}
]
...
I want to know if this part of code can be written differently, only with Mongoose helper methods of models ? Can I return a success and error if no stock are greater then 0 ?
ProductSchema.statics.substractStock = function (products) {
_.map(products, updateStock)
function updateStock(o) {
mongoose.model('Product').findById(o._id, function (err, product) {
return product
}).then(function(productDB){
if(productDB.stock > o.stock && productDB.stock > 0){
mongoose.model('Product').findOneAndUpdate(o._id, {$inc: {stock: -(o.stock)}}, {},
function (err, doc) {
//return success ??
}
);
} else {
//return 'no update'
}
});
}
};
This could be done with an atomic update where you can ditch the initial findById() call and include the comparison logic
if (productDB.stock > o.stock && productDB.stock > 0) { ... }
within the query as in the following:
function updateStock(o) {
mongoose.model('Product').findOneAndUpdate(
{
"_id": o._id,
"$and": [
{ "stock": { "$gt": o.stock } } ,
{ "stock": { "$gt": 0 } }
]
},
{ "$inc": { "stock": -(o.stock) } },
{ "new": true }, // <-- returns modified document
function (err, doc) {
// check whether there was an update
}
);
}
I would like to add a subdocument to an array if it doesn't already exist and then return the newly added subdocument (or at least the array of subdocuments) within one query. Here is an example document structure:
{
"name": "John Smith",
"folders": [
{
"folderName": "Breweries"
"updatedAt": 1450210046338,
"checkins": [
{
"facebookID": "123",
"checkinID": "3480809",
"addedOn": 1450210046338
},
{
"facebookID": "234",
"checkinID": "345254",
"addedOn": 1450210046339
}
],
},
{
"folderName": "Food"
"updatedAt": 1450210160277,
"checkins": [
{
"facebookID": "432",
"checkinID": "123545426",
"addedOn": 1450210160277
}
],
}
],
}
The nested query below checks to see if the new folder's name already exists in the folders array. If it doesn't already exist, it adds the new folder to the folders array:
(using mongoskin here)
mongodb.collection('users').findOne(
{facebookID: facebookID, 'folders.folderName': folderName},
function (err, result) {
if (err) {
deferred.reject(err);
} else if (result !== null) {
deferred.reject(new Error('Folder name already taken'));
} else {
mongodb.collection('users').findOne(
{facebookID: facebookID, 'folders.folderName': folderName},
function (err, result) {
if (err) {
deferred.reject(err);
} else if (result !== null) {
deferred.reject(new Error('Folder name already taken'));
} else {
mongodb.collection('users').findAndModify(
{facebookID: facebookID},
[],
{$addToSet: {folders: newFolder}},
{fields:{'folders': 1}, new: true},
function (err, result) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(result);
}
});
}
});
It seems like you should be able to do this in one query - but I couldn't find a way to achieve $setOnInsert functionality with array operators ($addToSet/$push).