Search code examples
mongodbmongooseaggregation-framework

Count field values dynamically in mongo aggregation


I have three collections listings, listingsCollections and profile. I used aggregation on listingsCollections and matched collectionID from listings. I used another lookup to add profile info to listings. My lookup response is like this

{
    //listingsCollections fields
    collectionID: 'abc', 
    collectionName: 'xyz',
    listings: [
        {
            // listings fields
            type: 'free',
            listingName: 'listing one',
            // profile fields
            ownerInfo: {
                name: 'user 1'
            }
        },
        {
            type: 'free',
            listingName: 'listing three',
            ownerInfo: {
                name: 'user 1'
            }
        },
        {
            type: 'paid',
            listingName: 'listing two',
            ownerInfo: {
                name: 'user 1'
            }
        },
    ]
}

I want to calculate type with count and total listings count.I don't want to pass hard coded type values in $addFields with $cond.

This is the disired output i am looking for.Is there any way to achieve this

{
    collectionID: 'abc', 
    collectionName: 'xyz',
    listingCount: {
        free: 2,
        paid: 1
    },
    totalListings: 3,
    listings: [
        {
            type: 'free',
            listingName: 'listing one',
            ownerInfo: {
                name: 'user 1'
            }
        },
        {
            type: 'free',
            listingName: 'listing three',
            ownerInfo: {
                name: 'user 1'
            }
        },
        {
            type: 'paid',
            listingName: 'listing two',
            ownerInfo: {
                name: 'user 1'
            }
        },
    ]
}

Solution

  • To create an object with unique keys, we need to use $arrayToObject + $objectToArray operators.

    Explanation:

    input:
    [{"type": "free"}, {"type": "free"}, {"type": "paid"}]
    --------------- $objectToArray --------------------
    [{"k": "type", "v": "free"}, {"k": "type", "v": "free"}, {"k": "type", "v": "paid"}]
    --------------- $arrayToObject --------------------
    {"free": 2, "paid": 1} // since objects cannot have same key for twice
    

    Try this one:

    db.collection.aggregate([
      {
        $addFields: {
          listingCount: {
            $arrayToObject: {
              $map: {
                input: "$listings.type",
                as: "type",
                in: {
                  k: "$$type",
                  v: {
                    $size: {
                      $filter: {
                        input: "$listings.type",
                        cond: {
                          $eq: [
                            "$$this",
                            "$$type"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          totalListings: {
            $size: "$listings"
          }
        }
      }
    ])
    

    MongoPlayground