MongoDB $mergeObjects

In MongoDB, the $mergeObjects aggregation pipeline operator combines multiple documents into a single document.

Syntax

The $mergeObjects operator supports two syntaxes.

Syntax 1:

{ $mergeObjects: [ <document1>, <document2>, ... ] }

Syntax 2:

{ $mergeObjects: <document> }

The first syntax accepts multiple arguments and the second syntax accepts one argument.

Example of Syntax 1 (Multiple Arguments)

The first syntax involves providing $mergeObjects with more than one argument/document. $mergeObjects then combines those documents into one.

Suppose we have a collection called users with the following document:

{
	"_id" : 1,
	"name" : {
		"f_name" : "Homer",
		"l_name" : "Simpson"
	},
	"contact" : {
		"email" : "[email protected]",
		"ph" : null
	}
}

We can use $mergeObjects to merge the name and contact fields:

db.users.aggregate(
  [
    {
      $project:
        { 
          user: { $mergeObjects: [ "$name", "$contact" ] }
        }
    }
  ]
).pretty()

Result:

{
	"_id" : 1,
	"user" : {
		"f_name" : "Homer",
		"l_name" : "Simpson",
		"email" : "[email protected]",
		"ph" : null
	}
}

In this case, we merged both fields into a single field called user. If we had more fields/documents we could have merged those too if we wanted.

Duplicate Field Names

If the documents to be merged contain duplicate field names, $mergeObjects overwrites the field as it merges the documents. Therefore, the field in the resulting document contains the value from the last document merged for that field.

Suppose we have the following document:

{
	"_id" : 2,
	"name" : {
		"f_name" : "Peter",
		"l_name" : "Griffin"
	},
	"contact" : {
		"email" : "[email protected]",
		"f_name" : "Bart"
	}
}

We can see that both documents contain a field named f_name.

Here’s what happens when we merge those documents:

db.users.aggregate(
  [
    { $match: { _id: 2 } },
    {
      $project:
        { 
          user: { $mergeObjects: [ "$name", "$contact" ] }
        }
    }
  ]
).pretty()

Result:

{
	"_id" : 2,
	"user" : {
		"f_name" : "Bart",
		"l_name" : "Griffin",
		"email" : "[email protected]"
	}
}

The f_name field in the resulting document contains Bart, which is the value from the last document that was merged.

Null Values

If merging a document with null, the resulting document will be returned without any changes.

But if all documents to be merged are null, then an empty document is returned.

Suppose we have the following documents:

{
	"_id" : 3,
	"name" : {
		"f_name" : "Hubert",
		"l_name" : "Farnsworth"
	},
	"contact" : null
}
{ "_id" : 4, "name" : null, "contact" : null }

Here’s what happens when we merge the name and contact fields in those two documents:

db.users.aggregate(
  [
    { $match: { _id: { $in: [ 3, 4 ] } } },
    {
      $project:
        { 
          user: { $mergeObjects: [ "$name", "$contact" ] }
        }
    }
  ]
)

Result:

{ "_id" : 3, "user" : { "f_name" : "Hubert", "l_name" : "Farnsworth" } }
{ "_id" : 4, "user" : {  } }

Examples of Syntax 2 (Single Argument)

Here are two examples that use the the single argument syntax.

$group Stage Accumulator

In the first example, $mergeObjects is used as a $group stage accumulator.

Suppose we have a collection called products with the following documents:

{
	"_id" : 1,
	"product" : "Shirt",
	"inventory" : {
		"blue" : 10,
		"red" : 2
	}
}
{
	"_id" : 2,
	"product" : "Shirt",
	"inventory" : {
		"green" : 3,
		"black" : 1
	}
}
{
	"_id" : 3,
	"product" : "Shorts",
	"inventory" : {
		"blue" : 2,
		"red" : 8
	}
}
{
	"_id" : 4,
	"product" : "Shorts",
	"inventory" : {
		"green" : 5,
		"black" : 3
	}
}

We can group these documents by their product field, and then use $mergeObjects to merge the inventory field for each group:

db.products.aggregate( [
   { $group: { 
     _id: "$product", 
     mergedProducts: { $mergeObjects: "$inventory" } 
     } 
    }
]).pretty()

Result:

{
	"_id" : "Shorts",
	"mergedProducts" : {
		"blue" : 2,
		"red" : 8,
		"green" : 5,
		"black" : 3
	}
}
{
	"_id" : "Shirt",
	"mergedProducts" : {
		"blue" : 10,
		"red" : 2,
		"green" : 3,
		"black" : 1
	}
}

Arrays

This example applies $mergeObjects to a single document that contains a field with an array of documents.

Suppose we have a collection called test with the following documents:

{
	"_id" : 1,
	"data" : [
		{
			"a" : 1,
			"b" : 2
		},
		{
			"c" : 3,
			"d" : 4
		}
	]
}

We can apply $mergeObjects to the data field:

db.test.aggregate(
  [
    {
      $project:
        { 
          result: { $mergeObjects: "$data" }
        }
    }
  ]
)

Result:

{ "_id" : 1, "result" : { "a" : 1, "b" : 2, "c" : 3, "d" : 4 } }

Missing Fields

$mergeObjects ignores any missing fields. That is, if you supply a field that doesn’t exist, it ignores it. If none of the fields exist, then it returns an empty document.

Example:

db.users.aggregate(
  [
    {
      $project:
        { 
          user: { $mergeObjects: [ "$name", "$oops" ] }
        }
    }
  ]
).pretty()

Result:

{ "_id" : 1, "user" : { "f_name" : "Homer", "l_name" : "Simpson" } }
{ "_id" : 2, "user" : { "f_name" : "Peter", "l_name" : "Griffin" } }
{ "_id" : 3, "user" : { "f_name" : "Hubert", "l_name" : "Farnsworth" } }
{ "_id" : 4, "user" : { } }

However, here’s what happens when none of the fields exist:

db.users.aggregate(
  [
    {
      $project:
        { 
          user: { $mergeObjects: [ "$wrong", "$oops" ] }
        }
    }
  ]
).pretty()

Result:

{ "_id" : 1, "user" : { } }
{ "_id" : 2, "user" : { } }
{ "_id" : 3, "user" : { } }
{ "_id" : 4, "user" : { } }

The result is an empty document.

It’s the same when using the single-argument syntax.

Example:

db.products.aggregate( [
   { $group: { 
     _id: "$product", 
     mergedProducts: { $mergeObjects: "$oops!" } 
     } 
    }
]).pretty()

Result:

{ "_id" : "Shorts", "mergedProducts" : { } }
{ "_id" : "Shirt", "mergedProducts" : { } }