1

I have the following code in node.js endpoint

let nodes = {};
if (req.query.fetchType == "tree") {
    nodes = await req.groupDocParam.getChildrenTree({ options: { lean: true } });
    if(req.query.includeOrganization == "true"){
        let data = await Group.findOne({ groupId: req.groupDocParam.groupId })
        data.children = [...nodes]
        nodes.splice(0, nodes.length) 
        nodes.push(data)
    }
} else if (req.query.fetchType == "children") {
    nodes = await req.groupDocParam.getImmediateChildren({});
}

else {
    nodes = await req.groupDocParam;
}
res.status(200).json({
    message: 'Fetched groups successfully.',
    items: nodes
});

At these four lines

 let data = await Group.findOne({ groupId: req.groupDocParam.groupId })
 data.children = [...nodes]
 nodes.splice(0, nodes.length) 
 nodes.push(data)

I expect nodes array to contain an object with new property children in it, and it does. However, when I test the endpoint, the newly added children property is not returned with payload:

Expected

items = [
   {
      "id": 21,
      "name": "test"
      // this is from line  data.children = [...nodes]
      "children": [Object, Object]
   }
]

Actual

   items = [
       {
          "id": 21,
          "name": "test"
       }
    ]

Is there some immutability issue here in nodes object?

Ratan Uday Kumar
  • 5,738
  • 6
  • 35
  • 54
positron
  • 3,643
  • 3
  • 22
  • 26
  • 1
    Add a `console.log(nodes);` before the response. Does it output the expected value? Also, what's the purpose of `nodes.splice(0, nodes.length)` and then immediately pushing `data` - why not just `nodes = [data];`? – Klaycon Nov 14 '19 at 02:50
  • I am logging nodes before the response and data is as expected. The reason I am emptying nodes before putting data there is because nodes is populated first and then its data is added to data["children"] which in turn is added to nodes. It's more of business logic requirement for the data model. – positron Nov 14 '19 at 02:59
  • @Phil Just did. Basically I want to add a new property to object that was returned from DB call, but it does not persist trough response return and object without added property is returned. – positron Nov 14 '19 at 03:51
  • How are you verifying that your _"four lines"_ are actually executed? I would suggest attaching a debugger but even some `console.log('here')` lines would be better than nothing – Phil Nov 14 '19 at 04:12
  • @Phil I step through code execution and see that change is there https://i.imgur.com/UGhq1vV.png – positron Nov 14 '19 at 04:46
  • The response contains the object that was created when nodes variable was first initialized at line 55 in screenshot. Then whatever I did on lines 57-60 does not make it into response, just the data from line 55. It's almost like nodes changed and then when it was returned, the changes are gone. I would think using 'let' would have something to do with it, but it's declared in the same block as response is send. – positron Nov 14 '19 at 04:52
  • Any chance you could show some screenshots from the client-side? Also make sure the client-side isn't caching the response – Phil Nov 14 '19 at 04:55
  • I am using Postman to execute the endpoint to verify purity of the call, and it doesn't contain newly added "children" property. – positron Nov 14 '19 at 05:12
  • I also tried changing response return to items:"test" and Postman call reflects that. – positron Nov 14 '19 at 05:15
  • @Phil I found the issue. I will update my post with results – positron Nov 14 '19 at 15:47

2 Answers2

1

When a query is executed against database (in my case MongoDB), what's returned is Mongoose Document, not JavaScript Object. As a result this object cannot be altered as is, it needs to be converted to Java Script object using lean() method:

By default, Mongoose queries return an instance of the Mongoose Document class. Documents are much heavier than vanilla JavaScript objects, because they have a lot of internal state for change tracking. Enabling the lean option tells Mongoose to skip instantiating a full Mongoose document and just give you the POJO.

More info here

With this, my code changed to the following and I get expected results on endpoint execution

let nodes = {};
let isIncludeOranization = false;

    if (req.query.fetchType == "tree") {
        nodes = await req.groupDocParam.getChildrenTree({ options: { lean: true } });
        if(req.query.includeOrganization == "true"){
            isIncludeOranization = true;
            await Group.findOne({ groupId: req.groupDocParam.groupId })
              .lean()
                .exec(function(err, data){
                   // perform data change here
                   data.children = [...nodes]
                   nodes.splice(0, nodes.length) 
                   nodes.push(data)

                   if(err && !err.statusCode){
                      err.statusCode = 500;
                      next(err);
                   }

                   // return response because we are done
                   res.status(200).json({
                   message: 'Fetched groups successfully.',
                   items: nodes
                });
            });
        }
    } else if (req.query.fetchType == "children") {
        nodes = await req.groupDocParam.getImmediateChildren({});
    }

    else {
        nodes = await req.groupDocParam;
    }

    if(!isIncludeOranization){
        res.status(200).json({
            message: 'Fetched groups successfully.',
            items: nodes
        });
    }
positron
  • 3,643
  • 3
  • 22
  • 26
0
 let data = await Group.findOne({ groupId: req.groupDocParam.groupId })
 data = data.toJson();
 data.children = [...nodes]
 nodes.splice(0, nodes.length) 
 nodes.push(data)

Add .toJson(), to turn your mongoose object into a plain JSON. I didn't test it works, but something along this lines would be this simplest answer. Why can't you modify the data returned by a Mongoose Query (ex: findById)

Sam Ulloa
  • 186
  • 2
  • 12