skip to Main Content

I am building an API endpoint, where a user can update nested entries of data in a nested array. The user can update one, many or all entries in that nested data, so I can’t hardcode the paths. In abstract, it looks something like this.

Data format

{
  "id": 4211,
  "foo": "some data",
  "bar": [
    {
      "id": 1
      "a": 123,
      "b": {
        "b1": "abc",
        "b2": "def"
      }
    }
  ]
}

Example updates

The update will be provided as a dotified version of an object.

update1 = {
  'b.b1': "vvv"
}

update2 = {
  'a': 1337,
  'b.b2': "zup"
}

Update function

async function updateNestedDataBar(baseId, id, update) {
  return await UserData.findOneAndUpdate(
    { id: baseId, 'bar.id': id }, // To make sure that the subdoc exists
    { $set: { 'bar.$': update },
    { new: true }
  );
}

Problem

When I update the data with the shown implementation, then it overwrites the whole bar object with the provided id. For example, update1 from above would result in this:

{
  "id": 4211,
  "foo": "some data",
  "bar": [
    {
      "b": {
        "b1": "vvv",
      }
    }
  ]
}

What I’ve tried so far

According to this answer to a similar question, I have tried to rewrite the code like this. Still it overrides the whole nested object.

UserData.findOneAndUpdate(
  { id: baseId, 'bar.id': id }, // To make sure that the subdoc exists
  { $set: { 'bar.$[outer]': update },
  { 
    arrayFilters: [{ 'outer.id': id }],
    new: true
  }
);

I have also tried to do it this way, by getting the whole document, modifiying it and then save() it. But I can’t get it to work and I also don’t want to fetch the whole document first. Plus I want to keep the benefit of the atomic operation with findOneAndUpdate.

Additionally, I have tried something crazy. Doesn’t work.

UserData.findOneAndUpdate(
  { id: baseId, 'bar.id': id }, // To make sure that the subdoc exists
  `bar.$.${update}`,
  { new: true }
);

Question

  1. How can I achieve the desired result?
  2. Is there a good documentation on the UpdateQuery type in mongoose? I can’t find anything in the offical docs that describes my issue.

2

Answers


  1. Chosen as BEST ANSWER

    In reference to collab-with-tushar-raj's answer, here a quick general purpose solution for anyone trying to solve a similar problem.

    function buildUpdateQuery(update, prefix) {
      let updateQuery = {};
    
      // Iterate through the keys in the update object
      Object.keys(update).forEach((key) => {
        updateQuery[prefix + key] = update[key];
      });
    
      return updateQuery;
    }
    
    
    async function updateNestedDataBar(baseId, id, update) {
      return await UserData.findOneAndUpdate(
        { id: baseId, 'bar.id': id },
        buildUpdateQuery(update, 'bar.$.'),
        { new: true }
      );
    }
    

  2. I would slightly modify your function to below:-

    async function updateNestedDataBar(baseId, id, update) {
      let updateQuery = {};
    
      // Iterate through the keys in the update object
      Object.keys(update).forEach((key) => {
      // Use dynamic key to update the nested fields
      updateQuery['bar.$.' + key] = update[key];
    });
    
    return await UserData.findOneAndUpdate(
      { id: baseId, 'bar.id': id }, // To make sure that the subdoc exists
      { $set: updateQuery },
      { new: true }
     );
    }
    

    This updateNestedDataBar function iterates through the keys of the update object, constructs the updateQuery dynamically, and then uses this query in the findOneAndUpdate method to update the specified nested fields within the bar array’s object.

    Now, you can use the updateNestedDataBar function with various update objects, such as update1 and update2 you provided

    const update1 = {
      'b.b1': 'vvv'
    };
    
    const update2 = {
      'a': 1337,
      'b.b2': 'zup'
    };
    
    // Call the function with update1
    const updatedData1 = await updateNestedDataBar(baseId, id, update1);
    
    // Call the function with update2
    const updatedData2 = await updateNestedDataBar(baseId, id, update2);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search