Search code examples
arraysmongodbaggregation-frameworknestjs

How to Modify MongoDB Object Located in an Array that is Nested in Another Array?


I have the problem where I need to modify a specific field of a object that is located in a array, but I would need to filter out the object within another array to get that specific object. For example, lets say I have the following document:

{
 _id: '1'
 name: 'Plan',
 builds: [
   {
     _id: '11'
     number: 20,
     type: 'Test Object 1',
     artifacts: [
      {
        link: 'helloworld.com',
        minioPath: 'test.zip'
        _id: '111'

      },
      {
        link: 'helloworld.com',
        minioPath: 'test15.zip',
        _id: '112'
      }
     ]
   },
   {
     _id: '12'
     number: 21,
     type: 'Test Object 2',
     artifacts: [
      {
        link: 'mongo.com',
        minioPath: 'test20.zip',
        _id: '211'
      },
      {
        link: 'mongo.com',
        minioPath: 'test25.zip',
        _id: '212'
      }
     ]
   }
 ]
}

What I would like to do is the following:

  1. Filter out the specific build object from the builds array.
  2. Filter out the specific artifact object from the artifacts array.
  3. Update the minioPath field to desired value.

For this problem, I am using MongoDB in NestJS to build an aggregation pipeline. This is what I have so far:

async getBuildItemArtifacts(id: string, buildItemId: string) {
    const buildPlanCondition = isMongoId(id)
      ? { _id: new Types.ObjectId(id) }
      : { key: id };

    const matchStage: PipelineStage.Match = { $match: buildPlanCondition };
    const filterBuildStage: PipelineStage[] = [
      {
        $project: {
          builds: {
            artifacts: {
              $filter: {
                input: '$artifacts',
                as: 'artifact',
                cond: {
                  $eq: [
                    '$$artifact._id',
                    new Types.ObjectId('111'),
                  ],
                },
              },
            },
          },
        },
      },
    ];

    const {
      builds: [result],
    } = await this.builds
      .aggregate<Partial<Build>>([matchStage, ...filterBuildStage])
      .then(([doc]) => {
        if (_.isUndefined(doc) || _.isEmpty(doc?.builds)) {
          throw new ApiError(HttpStatus.NOT_FOUND, 'No data found');
        }
        return doc;
      });

    return result;
  }

So far, I'm able to filter out the specific build object, but I'm now sure how to go about filtering out the specific artifact object. The aggregation pipeline got a little confusing, so any advice would be much appreciated!


Solution

  • The update is pretty easy using "arrayFilters".

    db.collection.update({
      _id: "1"
    },
    {
      "$set": {
        "builds.$[buildElem].artifacts.$[artElem].minioPath": "test42.zip"
      }
    },
    {
      "arrayFilters": [
        {"buildElem._id": "11"},
        {"artElem._id": "111"}
      ]
    })
    

    Try it on mongoplayground.net.