Search code examples
node.jsmongodbmongoose

Multiple same schema nested objects with a dynamic name inside an object


Is it possible to define a schema in a way that I can define only one key/value pair as a nested object and not having to repeat multiple pairs of just different keys with the same schema as a value.

From something like this:

const Nested = new mongoose.Schema(
  {
    name: String,
  }
);

const Main = new mongoose.Schema(
  {
    name: String,
    nested: {
      en: Nested,
      bg: Nested,
      fr: Nested,
      // etc...
    }
  }
);

To something like this:

...

const Main = new mongoose.Schema(
  {
    name: String,
    nested: {
      {somethingDynamic}: Nested
    }
  }
);

It's easy if nested can be an array, but it has to be an object.


Solution

  • The fields defined in the schema must be a limited set and predictable. A schema with infinite fields is meaningless and will cause an exception. The easy way is to create a schema builder function.

    import mongoose from 'mongoose';
    import { config } from '../../config';
    
    mongoose.set('debug', true);
    console.log(mongoose.version);
    
    const Nested = new mongoose.Schema({
        name: String,
    });
    
    const nestedSchemaBuilder = (keys, schema) => {
        return keys.reduce((acc, key) => {
            acc[key] = schema;
            return acc;
        }, {});
    };
    
    const keys = ['en', 'bg', 'fr'];
    const MainSchema = new mongoose.Schema({
        name: String,
        nested: nestedSchemaBuilder(keys, Nested),
    });
    const Main = mongoose.model('Main', MainSchema);
    
    (async function main() {
        try {
            await mongoose.connect(config.MONGODB_URI);
            // seed
            await Main.create([
                {
                    name: 'a',
                    nested: keys.reduce((acc, cur) => {
                        acc[cur] = { name: cur };
                        return acc;
                    }, {}),
                },
            ]);
            const doc = await Main.findOne();
            console.log(doc?.toObject());
        } catch (error) {
            console.error(error);
        } finally {
            await mongoose.connection.close();
        }
    })();
    

    Logs:

    7.3.4
    Mongoose: mains.insertOne({ name: 'a', nested: { en: { name: 'en', _id: ObjectId("64b12146362b767506a1a5c4") }, bg: { name: 'bg', _id: ObjectId("64b12146362b767506a1a5c5") }, fr: { name: 'fr', _id: ObjectId("64b12146362b767506a1a5c6") } }, _id: ObjectId("64b12146362b767506a1a5c3"), __v: 0}, {})
    Mongoose: mains.findOne({}, {})
    {
      nested: {
        en: { name: 'en', _id: new ObjectId("64b12146362b767506a1a5c4") },
        bg: { name: 'bg', _id: new ObjectId("64b12146362b767506a1a5c5") },
        fr: { name: 'fr', _id: new ObjectId("64b12146362b767506a1a5c6") }
      },
      _id: new ObjectId("64b12146362b767506a1a5c3"),
      name: 'a',
      __v: 0
    }