mongoose - get a nested array of objects from Model.find() and manipulate the inner array

Viewed 325

I am trying to get an array of objects with a certain ID from my database inside a route of my server, and then add change a property of an object inside the array (instead of returning an objectID to my client I want to return the Document as an object with the specific ID).

This is my code:

        let orders = await Order.find({restaurant: restaurantID, status: 'PROCESSING'})

        for(let order of orders){   //iterate through all orders
            for(let element of order.dishes){     //iterate through order.dishes array (called 'element' since the array contains objects)
                let dish = await Dish.findOne({_id: element._id})
                element['dish'] = dish  //create new property for the dish object
                delete element._id      //remove the ID property since it already exists inside the element.dish object
            }
        }

Every order object inside orders contains an array called dishes, which contains objects with the properties amount and an id. Since my frontend can't do much with the ID, I want to delete the id property and add a new property called dish, which contains the dish object the id points to.

The problem I am facing is that I don't know how to manipulate the orders arrays content. Whenever I parse orders to JSON and sent it inside my response, I recieve a JSON array of order objects just like I want to. But when I add the code I pasted above, it does not change anything inside my orders.

Whenever I log the element inside my for loops it looks like this: EmbeddedDocument {__parentArray: Proxy, __index: 0, $__parent: model, $__: InternalCache, $isNew: false, …} but when I parse it to JSON I recieve exactely what I want, namely this: {"amount":1,"_id":"6183b84fec1c3e109a2271be"}

Is orders even an array in this case? If not, what would be the easiest way to manipulate it/get the documents as an array?

This is what orders looks like when I watch it in my debug window:

price (get):ƒ () {\n        return this[getSymbol].call(this.$__[scopeSymbol] || this, path);\n      }
price (set):ƒ (v) {\n        this.$set.call(this.$__[scopeSymbol] || this, path, v);\n      }
restaurant (get):ƒ () {\n        return this[getSymbol].call(this.$__[scopeSymbol] || this, path);\n      }
restaurant (set):ƒ (v) {\n        this.$set.call(this.$__[scopeSymbol] || this, path, v);\n      }
status (get):ƒ () {\n        return this[getSymbol].call(this.$__[scopeSymbol] || this, path);\n      }
status (set):ƒ (v) {\n        this.$set.call(this.$__[scopeSymbol] || this, path, v);\n      }
timestamp (get):ƒ () {\n        return this[getSymbol].call(this.$__[scopeSymbol] || this, path);\n      }
timestamp (set):ƒ (v) {\n        this.$set.call(this.$__[scopeSymbol] || this, path, v);\n      }
user (get):ƒ () {\n        return this[getSymbol].call(this.$__[scopeSymbol] || this, path);\n      }
user (set):ƒ (v) {\n        this.$set.call(this.$__[scopeSymbol] || this, path, v);\n      }
__v (get):ƒ () {\n        return this[getSymbol].call(this.$__[scopeSymbol] || this, path);\n      }
__v (set):ƒ (v) {\n        this.$set.call(this.$__[scopeSymbol] || this, path, v);\n      }
__proto__:Model
length:1

And in postman, the res.body looks like this (almost exactely like it should but instead of the dishes object it contains only the id):

[
    {
        "_id": "6183b84fec1c3e109a2271bd",
        "user": "6166bc426181646198fc483c",
        "restaurant": "6176947ce8b10986b018930e",
        "dishes": [
            {
                "amount": 1,
                "_id": "6183b84fec1c3e109a2271be"
            },
            {
                "amount": 2,
                "_id": "6183b84fec1c3e109a2271bf"
            }
        ],
        "price": 30,
        "status": "PROCESSING",
        "timestamp": "2021-11-04T10:39:11.800Z",
        "__v": 0
    }
]

If this was already solved, please send me a link to the question/answer. I spent the last few hours trying to solve this and I already looked at a lot of questions but none of the answers were helpful for me

1 Answers

It is a common question. People are often disconcerted because they can't alter the data coming from MongoDB.

The thing is, Mongoose is kind of weird; it does not return simple JSON, but constructs and returns a collection of Mongoose objects. These objects have extra methods, like .save(), but above all their nested objects are immutable. Simply changing their properties have no effect.

In order to modify the data returned by Mongoose, you have two possibilities :

  1. Use .markModified()

It's a really weird concept, but the changes you make won't be applied until you mark them as modified. For instance :

element['dish'] = dish;
order.save();

This will have no effect. The name will remain unchanged in Mongo. You need .markModified() :

element['dish'] = dish;
order.markModified("dishes");
order.save(); // Works now :|
  1. Tell Mongoose to return simple JSON

You can prevent Mongoose from constructing its own objects by using .lean(). this will return simple JSON data that can be tampered with at will.

let orders = await Order
           .find({restaurant: restaurantID, status: 'PROCESSING'})
           .lean()
           .exec(); // Also add .exec(), it returns a true Promise and not a thenable 

Bonus : lean() is faster and lighter.

Related