Updating an object value in a nested array

I’ve been trying to use positional operators to update an object property value to no avail. The code is straightforward and should work based on what I’ve watched video-wise and read documentation-wise. However, I’ve received an error every time without being able to make any progress in solving it.

		await player.updateOne({ 'pity.type': type }, { $set: { 'pity.$.totalWishes': 1 } });

Using a positional operator, I’m attempting to set the totalWishes of the specific banner type to 1 but receive the error below.

MongoServerError: Cannot create field 'type' in element

Any and all help is appreciated.

1 Like

Hi @ProbablyPi ,

For updating nested arrays specific elements you need to use a syntax called arrayFilters.

See more examples here :

Thanks
Pavel

Hi, can you please share a sample document where you are trying to update this?

1 Like

Hi @shrey_batra ,

The document will probably look like :

{
pity : [
{ type : xxx,
  totalWishes : 0
}
]
}

The command should look something like :

db.player.updateOne(
   {"pity.type" : type },
   { $set: { "pity.$[elem].totalWishes" : 1 } },
   { arrayFilters: [ { "elem.type": type } ] }
)

Thanks

Hi and welcome to the forums @ProbablyPi,

Second @shrey_batra if you could please add:

  1. sample document, and
  2. expected output

So we don’t assume too many details that may actually draw you back from doing it.

Thank you @santimir ,

The document looks like Pavel suggested

{
   "pity":[
      { "type":"weapon", "totalWishes":0 },
      { "type":"character", "totalWishes":0 },
      { "type":"standard", "totalWishes":0 }
   ]
}

However, I’ve tried what Pavel suggested before I made this post, and not even that worked. The expected output is actually to increment totalWishes, but I thought there was an issue with using the $inc operator so I tried using $set to no avail.

Hi Pavel,

Thanks for the suggestion, however, I tried a similar method before making this post without any success. Though, I will continue to try today with the array filters you mentioned earlier and see if that is the solution I’ve been looking for.

@ProbablyPi

See example here

To update all

db.collection.update({},
{
  $inc: {
    "pity.$[].totalWishes": 1
  }
})

Or with condition:

db.collection.update({},
{
  $inc: {
    "pity.$[elem].totalWishes": 1
  }
},
{
  arrayFilters: [
    {
      "elem.type": "weapon"
    }
  ]
})

That’s why I asked input and output. I may still be misunderstanding :laughing:

You can also filter in the first field, and use updateOne if necessary etc. You will work it out.

@santimir

Tested with no errors! Though, the document itself doesn’t increment whatsoever. I’m quite confused now, I’ve double-checked that the types are correct, and made sure it’s fetching the correct document. Any clue why it isn’t incrementing? If it’s any help, I’m using mongoose ver 6.1.6

@ProbablyPi

Would you share a screenshot of the code?

Make sure you replace “collection” by the collection name, and that you’re in the right database. It may be useful to create a test collection if you haven’t done it already.

Also, from mongoose docs:

  • Model.update: Updates one document in the database without returning it.

@santimir

Yep, in the right database and I’m using my proper collection, though upon logging it outputs

{ acknowledged: false }

Would this have anything to do with it? And as requested, here is the code I’m executing

const player = await client.database.fetchPlayerData(application.user.id);

const test = await player.updateOne({}, {
$inc: { 'pity.$[elem].totalWishes': 1 },
arrayFilters: [{ 'elem.type': type }]
});
console.log(test);

The fetchPlayerData function looks like this.
image

If I can provide anything else that might help, please let me know.

Uhmm, I am not sure I can debug from that because I don’t know enough of Mongoose.

But JIC it helps other users, what does client.database resolve to? And does it store fetchPlayerData() method?

The acknowledge false may indeed come from updateOne(). You could also console.log('log', player, 'and', application.user).

The client.database call returns the object below.

{
  _id: new ObjectId("61fcce41ae207a386fc5a003"),
  userId: '490243269096112129',
  totalWishes: 270,
  pity: [
    {
      type: 'standard',
      totalWishes: 10,
      fourStarPity: 0,
      fiveStarPity: 10,
      wishHistory: [Array]
    },
    {
      type: 'weapon',
      totalWishes: 0,
      fourStarPity: 0,
      fiveStarPity: 0,
      wishHistory: []
    },
    {
      type: 'character',
      totalWishes: 260,
      fourStarPity: 0,
      fiveStarPity: 70,
      wishHistory: [Array]
    }
  ],
  createdOn: 2022-02-04T06:56:59.521Z,
  lastUpdated: 2022-02-04T06:56:59.521Z,
  __v: 1
}

I do know for 100% certainty that both player and application.user return what’s requested. I think it has to pertain with the mongo call itself. Previously I had to use .findIndex() on the player object and do pity.${typeIndex}.totalWishes and that seemingly worked. But it’s a bit slower than using strictly mongo methods.

After doing a bit of looking, acknowledge false seems to be when the call failed to write to the database.

So player is a Document instance, and you probably extended that or a parent class with the async methods. We should then read Document.updateOne()

They use something like (mind the first field);

player.updateOne({
  $inc: {
    "pity.$[elem].totalWishes": 1
  }
},
{
  arrayFilters: [
    {
      "elem.type": "weapon"
    }
  ]
}, { w: 1 }, callback);

Which sends the _id along.

@santimir

Sorry if it’s obvious, but what is w in this case?

  • w option to request acknowledgment that the write operation has propagated to a specified number of mongod instances.

Google about write concerns to get deeper into it.

@ProbablyPi any luck?

Hi @santimir

I took a break for awhile, to take a look at the problem with fresh eyes. Using the code you provided above, provides this error Error: Could not find path "pity.0.type" in schema, do you think it may be smarter to use regular properties or objects instead of an array of objects?

1 Like

@ProbablyPi that’s a shame. Maybe rewrite like this?

player.updateOne({
  $inc: {
    "pity.$[elem].totalWishes": 1
  }
},
{
  arrayFilters: [
    {
      "elem.type": "weapon"
    }
  ],
  w: 1 
}, (e,r)=>console.log(e,r));

It is not very clear t me from the mongoose docs if the arrayFilters should be in the same document. But now it looks the same than MongoDB docs at least.

The last thing I could think of it is that some of those packages (maybe mongodb native driver) is outdated. You can check with npm outdated.


I don’t think array of objects is a big deal, this is just part of learning how to use it. But you should try to make the Schema as simple as possible, keeping it useful.

Might I suggest using MongoDB Shell to make sure that the issue is strictly in your code rather than in the database/update syntax?

db.playerCollection.updateOne( { <find-condition> }, {$inc: { } } etc) 

should work so if it works from the shell, but not from your code then you’ll know that you need to figure out what’s wrong in your code. But if it gives an error in the shell then you’ll know that something is wrong with the data and you can stop debugging the code and debug data+update syntax on its own.

Asya

1 Like