Search code examples
node.jsmongodbmongoose

Populate array inside object in mongoose


I have a company model which looks like this:

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const CompanySchema = new Schema(
  {
    companyName: String,
    settings: {
      type: {
        priceVisible: Boolean,
        allowPickupAddressAddition: Boolean,
        paymentMethodsAvailable: [
          { type: Schema.Types.ObjectId, ref: "PaymentMethod" },
        ],
      },
    },    
  }
);

const Company = mongoose.model("Company", CompanySchema);

module.exports = Company;

And I want to populate the values store in paymentMethodsAvailable array. Here is relevant controller code:

const company = await Company.findOne({ _id: id }).populate([
      {
        path: "settings",
        populate: [{path: "paymentMethodsAvailable"}]
      },
    ]);

But this doesn't work as expected. I can see that it might be trying to populate settings object, and fails there. Is there a way in which I can tell mongoose to populate settings.paymentMethodsAvailable ?


Solution

  • You should notice The type Key:

    type is a special property in Mongoose schemas. When Mongoose finds a nested property named type in your schema, Mongoose assumes that it needs to define a SchemaType with the given type.

    • If the type is a nested property of settings, the Mongoose schema should be:
    const CompanySchema = new mongoose.Schema({
        companyName: String,
        settings: {
            type: {
                type: {
                    priceVisible: Boolean,
                    paymentMethodsAvailable: [{ type: mongoose.Schema.Types.ObjectId, ref: 'PaymentMethod' }],
                },
            },
        },
    });
    
    // seed
    const [p1] = await PaymentMethod.create([{ name: 'a' }, { name: 'b' }]);
    const [c1] = await Company.create([
      {
        companyName: 'c-a',
        settings: {
          type: {
            priceVisible: true,
            paymentMethodsAvailable: [p1],
          },
        },
      },
    ]);
    
    // populate
    const company = await Company.findOne({ _id: c1?._id }).populate('settings.type.paymentMethodsAvailable');
    console.log(util.inspect(company?.toObject(), false, null));
    

    The populate path is settings.type.paymentMethodsAvailable.

    Output:

    {
      settings: {
        type: {
          priceVisible: true,
          paymentMethodsAvailable: [
            {
              _id: new ObjectId("64abce807233874fe5733d10"),
              name: 'a',
              __v: 0
            }
          ],
          _id: new ObjectId("64abce807233874fe5733d15")
        }
      },
      _id: new ObjectId("64abce807233874fe5733d14"),
      companyName: 'c-a',
      __v: 0
    }
    
    • If the type is not a nested property of settings, the Mongoose schema is like yours:
    const CompanySchema = new mongoose.Schema({
        companyName: String,
        settings: {
            type: {
                priceVisible: Boolean,
                paymentMethodsAvailable: [{ type: mongoose.Schema.Types.ObjectId, ref: 'PaymentMethod' }],
            },
        },
    });
    
    // seed
    const [p1] = await PaymentMethod.create([{ name: 'a' }, { name: 'b' }]);
    const [c1] = await Company.create([
      {
        companyName: 'c-a',
        settings: {
          priceVisible: true,
          paymentMethodsAvailable: [p1],
        },
      },
    ]);
    
    // populate
    const company = await Company.findOne({ _id: c1?._id }).populate('settings.paymentMethodsAvailable');
    console.log(util.inspect(company?.toObject(), false, null));
    

    The populate path is settings.paymentMethodsAvailable.

    Output:

    {
      _id: new ObjectId("64abd00acd28f4120a317c2e"),
      companyName: 'c-a',
      settings: {
        priceVisible: true,
        paymentMethodsAvailable: [
          {
            _id: new ObjectId("64abd00acd28f4120a317c2a"),
            name: 'a',
            __v: 0
          }
        ],
        _id: new ObjectId("64abd00acd28f4120a317c2f")
      },
      __v: 0
    }