Mongoose: How to store multiple schema types in an mongoose subdocuments array

Created on 24 Jan 2017  路  13Comments  路  Source: Automattic/mongoose

var modelSchema = new mongoose.Schema({
name: {
type: String
},
personas: [typeA | typeB] // need one of two types here(typeA schema,typeB schema)
});

var bigSchema = new mongoose.Schema({
title: {
type: String
},
model: modelSchema
})

Most helpful comment

= 4.8.0 supports this:

var eventSchema = new Schema({ message: String },
  { discriminatorKey: 'kind', _id: false });

var batchSchema = new Schema({ events: [eventSchema] });
batchSchema.path('events').discriminator('Clicked', new Schema({
  element: String
}, { _id: false }));
batchSchema.path('events').discriminator('Purchased', new Schema({
  product: String
}, { _id: false }));

var MyModel = db.model('gh2723', batchSchema);
var doc = {
  events: [
    { kind: 'Clicked', element: 'Test' },
    { kind: 'Purchased', product: 'Test2' }
  ]
};
MyModel.create(doc).
  then(function(doc) {
    // doc.events[0] has an 'element' property, doc.events[1] has a 'product' property
  });

All 13 comments

You have to use a Mixed type: http://mongoosejs.com/docs/schematypes.html

Note: you will have to call doc.markModified() on the fields that you change if you go down this route.

Mixed type allows anything (without validating). As typeA and typeB are schemas and i want to validate them.
We can do this for normal schema using discriminators or "mongoose-schema-extend". But at subdocument level i didn't found any solution.

Yeah I don't think what you're asking for in terms of specifying a type like typeA | typeB is possible unless you compose your own custom schema for the subdocument or you used a Mixed type.

= 4.8.0 supports this:

var eventSchema = new Schema({ message: String },
  { discriminatorKey: 'kind', _id: false });

var batchSchema = new Schema({ events: [eventSchema] });
batchSchema.path('events').discriminator('Clicked', new Schema({
  element: String
}, { _id: false }));
batchSchema.path('events').discriminator('Purchased', new Schema({
  product: String
}, { _id: false }));

var MyModel = db.model('gh2723', batchSchema);
var doc = {
  events: [
    { kind: 'Clicked', element: 'Test' },
    { kind: 'Purchased', product: 'Test2' }
  ]
};
MyModel.create(doc).
  then(function(doc) {
    // doc.events[0] has an 'element' property, doc.events[1] has a 'product' property
  });

Most use cases of this, can actually be simplified using multiple properties. For example, instead of

personas: [typeA | typeB]
 ```
you could do

personasTypeA: [typeA],
personasTypeB: [typeB]
```

In my case, it was files and folders (instead of having a flattened array of mixed file/folder, just separated them and I've let folders be recursive)

@vkarpov15 will discrimantors work with a typed field such as in the following schema?

const TypesSchema = new Schema({ 
  type: {type:Number,enum:Object.values(constants.TYPES)} 
},{descriminatorKey:'type',_id:false});

I want to have a field called type with with types defined by the constants objects, and this field will be used to descriminate among the types schemas. each type has its own schema

@r3wt I believe so. However, you should use type: String, because type: Number doesn't support enum

@vkarpov15 i can't use String, because the constant values are int and strict equality is used everywhere. I just removed the enum for now

@r3wt we'll have enum for numbers in 5.8.0, see #8139

@r3wt we'll have enum for numbers in 5.8.0, see #8139

I appreciate it, but there was no way around it so we we ended up migrating all codebase to String and it is working without any issue now. I am glad to see this feature though, now i can save disk space by using more numeric enums for hardcoded values

= 4.8.0 supports this:

var eventSchema = new Schema({ message: String },
  { discriminatorKey: 'kind', _id: false });

var batchSchema = new Schema({ events: [eventSchema] });
batchSchema.path('events').discriminator('Clicked', new Schema({
  element: String
}, { _id: false }));
batchSchema.path('events').discriminator('Purchased', new Schema({
  product: String
}, { _id: false }));

var MyModel = db.model('gh2723', batchSchema);
var doc = {
  events: [
    { kind: 'Clicked', element: 'Test' },
    { kind: 'Purchased', product: 'Test2' }
  ]
};
MyModel.create(doc).
  then(function(doc) {
    // doc.events[0] has an 'element' property, doc.events[1] has a 'product' property
  });

Hey @vkarpov15, I can't call discriminator method on schema, I need to create the model first but your example above shows otherwise. Can you please clarify?

@bbhopesh Yes, you right Schema does have method discriminator, but
batchSchema.path('events') returns not a Schema, it method actually returns Schema | DocumentArrayPath. But @types/mongoose does not have right declaration, i guess.

You can find more info here

@mkovel is correct, you can call discriminator() on certain schema types. FWIW batchSchema.path('events') does NOT return a schema, it returns a SchemaType

Was this page helpful?
0 / 5 - 0 ratings