Don Scott
Don Scott

Reputation: 3457

Encapsulate/Transform an Array into an Array of Objects

I am currently in the process of modifying a schema and I need to do a relatively trivial transform using the aggregation framework and a bulkWrite.

I want to be able to take this array:

{ 
     ...,
     "images" : [
        "http://example.com/...", 
        "http://example.com/...", 
        "http://example.com/..."
    ] 
}

and aggregate to a similar array where the original value is encapsulated:

{ 
     ...,
     "images" : [
        {url: "http://example.com/..."}, 
        {url: "http://example.com/..."}, 
        {url: "http://example.com/..."}
    ] 
}

This slow query works, but it is ridiculously expensive to unwind an entire collection.

[
    {
        $match: {}
    },
    {
        $unwind: {
            path : "$images",
        }
    },
    {
        $group: {
            _id: "$_id",
            images_2: {$addToSet: {url: "$images"}}
        }
    },
]

How can this be achieved with project or some other cheaper aggregation?

Upvotes: 2

Views: 631

Answers (2)

Sede
Sede

Reputation: 61225

You don't need to use the bulkWrite() method for this.

You can use the $map aggregation array operator to apply an expression to each element element in your array.

Here, the expression simply create a new object where the value is the item in the array.

let mapExpr = {
    "$map": {
        "input": "$images",
        "as": "imageUrl",
        "in": { "url": "$$imageUrl }
    }
};

Finally you can use the $out aggregation pipeline operator to overwrite your collection or write the result into a different collection.

Of course $map is not an aggregation pipeline operator so which means that the $map expression must be use in a pipeline stage.

The way you do this depends on your MongoDB version.

The best way is in MongoDB 3.4 using $addFields to change the value of the "images" field in your document.

db.collection.aggregate([
    { "$addFields": { "images": mapExpr }},
    { "$out": "collection }
])

From MongoDB 3.2 backwards, you need to use the $project pipeline stage but you also need to include all the other fields manually in your document

db.collection.aggregate([
    { "$project": { "images": mapExpr } },
    { "$out": "collection }
])

Upvotes: 1

kkkkkkk
kkkkkkk

Reputation: 7748

$map expression should do the job, try this:

db.col.aggregate([
  {
    $project: {
      images: {
        $map: {
          input: '$images',
          as: 'url',
          in: {
            url: '$$url'
          }
        }
      }
    }
  }
]);

Upvotes: 3

Related Questions