How to flatten a subdocument into root level in MongoDB?

For example, if I have a document like this

{
  a: 1,
  subdoc: {
    b: 2,
    c: 3
  }
}

How can I convert it into a format like this? (without using project)

{
  a: 1,
  b: 2,
  c: 3
}

Solution 1:

You can use MongoDB projection i.e $project aggregation framework pipeline operators as well. (recommended way). If you don't want to use project check this link

db.collection.aggregation([{$project{ . . }}]);

Below is the example for your case:

db.collectionName.aggregate
([
    { $project: { a: 1, 'b': '$subdoc.b', 'c': '$subdoc.c'} }
]);

Gives you the output as you expected i.e.

    {
        "a" : 1,
        "b" : 2,
        "c" : 3
    }

Solution 2:

You can use $replaceRoot with a $addFields stage as follows:

db.collection.aggregate([
    { "$addFields": { "subdoc.a": "$a" } },
    { "$replaceRoot": { "newRoot": "$subdoc" }  }
])

Solution 3:

We can do this with $replaceWith an alias for $replaceRoot and the $mergeObjects operator.

let pipeline = [
   {
      "$replaceWith": {
         "$mergeObjects": [ { "a": "$a" }, "$subdoc" ]
      }
   }
];

or

let pipeline = [
    {
        "$replaceRoot": {
            "newRoot": {
                "$mergeObjects": [{ "a": "$a" }, "$subdoc" ]
            }
        }
    }
];

db.collection.aggregate(pipeline)

Solution 4:

Starting Mongo 4.2, the $replaceWith aggregation operator can be used to replace a document by another (in our case by a sub-document) as syntaxic sugar for the $replaceRoot mentioned by @chridam.

We can thus first include within the sub-document the root field to keep using the $set operator (also introduced in Mongo 4.2 as an alias for $addFields) and then replace the whole document by the sub-document using $replaceWith:

// { a: 1, subdoc: { b: 2, c: 3 } }
db.collection.aggregate([
  { $set: { "subdoc.a": "$a" } },
  { $replaceWith: "$subdoc" }
])
// { b: 2, c: 3, a: 1 }