Simplify foreach to update a mongoDB doc that has nested objects and arrays

Asked

Viewed 100 times

1

I want to update the value of the 'shouldSendAlert' key in a mongoDB document that is in the following structure:

{
    "_id" : ObjectId("5c61c4db46d18e1092c5b024"),
    "service" : "SRVPVD",
    "menu" : [ 
        {
            "sub" : [ 
                {
                    "options" : [ 
                        {
                            "item" : [ 
                                {
                                    "name" : "",
                                    "actions" : [ 
                                        {
                                            "name" : "communicateClient",
                                            "value" : true
                                        }, 
                                        {
                                            "name" : "shouldSendAlert",
                                            "value" : false
                                        }
                                    ]
                                }
                            ],
                            "name" : "Technology Support"
                        }, 
                        {
                            "item" : [ 
                                {
                                    "name" : "",
                                    "actions" : [ 
                                        {
                                            "name" : "communicateClient",
                                            "value" : true
                                        }
                                    ]
                                }
                            ],
                            "name" : "Company Support"
                        }
                    ],
                    "name" : "Support"
                }, 
                {
                    "name" : " FAQ"
                }
            ],
            "name" : "Help"
        }
    ]
}

I managed to achieve this goal, with a multiple $elemMatch query and using a foreach for each array within JSON to get to 'shouldSendAlert':

{
    let menuItems = db.getCollection('menumodels').find({menu: {$elemMatch: {name: 'Help',sub: {$elemMatch: {name: 'Support',motivos: {$elemMatch: {name: 'Technology Support'}}}}}}});

    menuItems.forEach((r) => {
        r.menu.forEach(menuItem => {
            if (menuItem.name == 'Help') {
                menuItem.sub.forEach(sub => {
                    if (sub.name == 'Support') {
                        sub.motivos.forEach(motivo => {
                            if (motivo.name == "Technology Support") {
                                motivo.item[0].actions.forEach(action => {
                                    if (action.name == 'shouldSendAlert') {
                                        action.value = true;
                                        db.getCollection('menumodels').update({_id: r._id}, {$set: {menu: r.menu}})
                                    }
                                })
                            }
                        })
                    }
                })
            }
        })
    });
}

Is it necessary, in terms of performance, to do this operation in a more intelligent way? Use all of these $elemMatch and foreach within foreach, significantly impact performance?

Thanks for the tips.

  • I don’t get it, but is it something like that? structure.menu.find(x => x.name === 'Help').sub.find(x => x.name === 'Support').options.find(x => x.name === 'Technology Support'). item[0].actions.find(x => x.name === 'shouldSendAlert'). value = true;

  • @Rdyego, as I mentioned in the question, the code works. If you notice the line db.getCollection('menumodels').find({menu: {$elemMatch: {name: 'Help',sub: {$elemMatch: {name: 'Support',motivos: {$elemMatch: {name: 'Technology Support'}}}}}}}); The query returns me an array with the expected document. I want to know if it needs to be refactored for performance reasons. For example: using multiple $elemMatch nested, is very expensive?

  • There are 5 nested foreach s, this is very complex. Even for maintenance is not simple.

1 answer

1


I don’t know if mongoDB has functions that help in this case, but you can do a function that looks for a path in an object and returns the value you find.

In the example I created below I added an argument that I usually use to have a pointer to an object created by me to make sure that the search failed. Note that in this function, if the result you are looking for is undefined and that is a result that you want to return the function has to be adjusted. But in the case that shows in the question this does what you look for.

const complexObject = {
  foo: [{
    bar: 'baz',
    nestedFoo: {
      hello: 'world'
    }
  }]
};

function findPathValue(obj, path, noValue) {
  return path.split('.').reduce((value, key) => {
    const keyValue = value[key];
    return typeof keyValue === 'undefined' ? noValue : keyValue;
  }, obj);
};
const noResult = {};

const willFind = findPathValue(complexObject, 'foo.0.nestedFoo.hello', noResult);
const willFail = findPathValue(complexObject, 'foo.bar.baz', noResult);

console.log(willFind, willFind === noResult); // world false
console.log(willFail, willFail === noResult); // {} true

  • But what if you don’t know the position of the array that has the value I want? mongoDB allows you to use wildcard * only once per query. In that case I will have no way to escape from foreach, it is not?

  • 1

    @Matheuspedro O forEach is never good in this case. You should use how much the .find() or .some(). If you don’t know the position in the array which is the reference? the key? but some objects in the same array have the same key...

  • the reference is the key value name.

  • @Matheuspedro then wants to know if a given object has a key with a specific value in it. That’s it?

  • exactly! In addition, I also want to change the value of this key. I managed to do this but had to use a lot of forEach. In that case, I think I could use the .find() to tell me what I want in each array, and then change the value of shouldSendAlertusing a db.getCollection('menumodels').update simple.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.