Home > front end >  How do I get replaceRoot to return an object instead of an array?
How do I get replaceRoot to return an object instead of an array?

Time:12-15

I have 2 collections, ratePlans and properties.

Properties has an array of cancelPolicies as a subdocument and ratePlans has cancelPolicyId to reference this.

I am trying to lookup the cancellation policy for a given ratePlan.

$lookup: {
  from: "properties",
  let: { propertyId: "$propertyId", cancelPolicyId: "$cancelPolicyId" },
  pipeline: [
    { 
      $match: { $expr: { $eq: [ "$_id", "$$propertyId" ] } }
    },
    {
      $unwind: {
        path: "$cancelPolicies",
        preserveNullAndEmptyArrays: true
      }
    },
    { 
      $match: { $expr: { $eq: [ "$cancelPolicies._id", "$$cancelPolicyId" ] } }
    },
    // I tried this, but it does nothing. I also tried putting it after replaceRoot:
    // {
    //   $unwind: {
    //     path: "$cancelPolicies",
    //     preserveNullAndEmptyArrays: true
    //   }
    // },
    {
      $replaceRoot: { newRoot: "$cancelPolicies" }
    },
  ],
  as: "cancelPolicy"
}

This works, however cancelPolicy is now an array of length 1 instead of an object. Is it possible to have cancelPolicy be the actual object instead of an array?

CodePudding user response:

The problem doesn't have to do with $replaceRoot directly. In the question it's noted that the field with an unexpected type is cancelPolicy. The cancelPolicy field, not to be confused with cancelPolicies or cancelPolicyId, contains the result of the $lookup. That is to say that cancelPolicy is the as parameter of the $lookup which is defined as follows in the documentation (emphasis added):

Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten.

There is nothing that you can do inside of the $lookup, including in its nested pipeline, to coerce the type into anything other than an array.

The solution is to modify that field in these new documents in a subsequent stage. Most commonly this is achieved with an $unwind stage. So the aggregation would look something like this:

$lookup: {
  from: "properties",
  let: { propertyId: "$propertyId", cancelPolicyId: "$cancelPolicyId" },
  pipeline: [
    { 
      $match: { $expr: { $eq: [ "$_id", "$$propertyId" ] } }
    },
    {
      $unwind: {
        path: "$cancelPolicies",
        preserveNullAndEmptyArrays: true
      }
    },
    { 
      $match: { $expr: { $eq: [ "$cancelPolicies._id", "$$cancelPolicyId" ] } }
    },
    {
      $replaceRoot: { newRoot: "$cancelPolicies" }
    },
  ],
  as: "cancelPolicy"
},
{
  $unwind: "$cancelPolicy"
}
]

You can include the preserveNullAndEmptyArrays parameter in there as well depending on whether you are looking to perform a logical inner join or a left join.

  • Related