简体   繁体   中英

Mongoose Mixed type field with dates

I'm creating an app where a user can create various types of custom fields, and I'd like to store those fields in mongo. The types will include String, Number, and Date. My schema looks like this:

const MetaSchema = new mongoose.Schema({
  key: String,
  value: {type: mongoose.Schema.Types.Mixed},
  type: String,
  created_at: {type: Date, default: Date.now}
});

This works great, and I can store my data as expected. The issue is, when I want to store a date for instance, that gets sent across to the server in ISO format, a payload that might look like:

{
  "key": "Contract Signed",
  "value": "2016-04-06T22:35:11.540Z",
  "type": "date"
}

Any way I can get mongo / mongoose to treat and store this like a date instead of a string? If I set this to type date then I think it would do the trick, but I have to save just about anything they can come up with for custom fields. Thanks so much!

TLDR: Can a mixed data type in mongoose / mongo be treated differently based on the type of data being inserted (IE Date vs String).

Using mongoose discriminators is probably the way to go here. They actually work with their own "type" ( default __t but can be overridden ) property within the stored documents which allows mongoose to actually apply a kind of "model" to each object with it's own attached schema.

As a brief example:

var async = require('async'),
    util  = require('util'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/things');
mongoose.set("debug",true);

function BaseSchema() {

  Schema.apply(this,arguments);

  this.add({
    key: String,
    created_at: { type: Date, default: Date.now }
  });

}

util.inherits(BaseSchema,Schema);

var metaSchema = new BaseSchema();

var stringSchema = new BaseSchema({
  value: String
});

var numberSchema = new BaseSchema({
  value: Number
});

var dateSchema = new BaseSchema({
  value: Date
});

var MetaModel = mongoose.model('MetaModel',metaSchema),
    StringModel = MetaModel.discriminator('StringModel', stringSchema),
    NumberModel = MetaModel.discriminator('NumberModel', numberSchema),
    DateModel = MetaModel.discriminator('DateModel', dateSchema);

async.series(
  [
    function(callback) {
      MetaModel.remove({},callback);
    },
    function(callback) {
      async.each(
        [
          { "model": "StringModel", "value": "Hello" },
          { "model": "NumberModel", "value": 12 },
          { "model": "DateModel", "value": new Date() }
        ],
        function(item,callback) {
          mongoose.model(item.model).create(item,callback)
        },
        callback
      );
    },
    function(callback) {
      MetaModel.find().exec(function(err,docs) {
        console.log(docs);
        callback(err);
      });
    },
    function(callback) {
      DateModel.findOne().exec(function(err,doc) {
        console.log(doc);
        callback(err);
      });
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
)

So since these are basically "related" I am defining a "Base" schema with the common elements. Then of course there are separate schemas for each "type". The actual assignment to the core "model" happens in these lines:

var MetaModel = mongoose.model('MetaModel',metaSchema),
    StringModel = MetaModel.discriminator('StringModel', stringSchema),
    NumberModel = MetaModel.discriminator('NumberModel', numberSchema),
    DateModel = MetaModel.discriminator('DateModel', dateSchema);

This means that MetaModel actually defines the collection and "default" schema assingment. The following lines use .discriminator() from that model in order to define the other "types" of documents that will be stored in the same collection.

With the debugging output on to show what is happening, the listing produces something like this:

Mongoose: metamodels.remove({}) {}
Mongoose: metamodels.insert({ value: 'Hello', __t: 'StringModel', created_at: new Date("Thu, 07 Apr 2016 00:24:08 GMT"), _id: ObjectId("5705a8a8443c0f74491bdec0"), __v: 0 })
Mongoose: metamodels.insert({ value: 12, __t: 'NumberModel', created_at: new Date("Thu, 07 Apr 2016 00:24:08 GMT"), _id: ObjectId("5705a8a8443c0f74491bdec1"), __v: 0 })
Mongoose: metamodels.insert({ value: new Date("Thu, 07 Apr 2016 00:24:08 GMT"), __t: 'DateModel', created_at: new Date("Thu, 07 Apr 2016 00:24:08 GMT"), _id: ObjectId("5705a8a8443c0f74491bdec2"), __v: 0 })
Mongoose: metamodels.find({}) { fields: undefined }
[ { created_at: Thu Apr 07 2016 10:24:08 GMT+1000 (AEST),
    __t: 'StringModel',
    __v: 0,
    value: 'Hello',
    _id: 5705a8a8443c0f74491bdec0 },
  { created_at: Thu Apr 07 2016 10:24:08 GMT+1000 (AEST),
    __t: 'NumberModel',
    __v: 0,
    value: 12,
    _id: 5705a8a8443c0f74491bdec1 },
  { created_at: Thu Apr 07 2016 10:24:08 GMT+1000 (AEST),
    __t: 'DateModel',
    __v: 0,
    value: Thu Apr 07 2016 10:24:08 GMT+1000 (AEST),
    _id: 5705a8a8443c0f74491bdec2 } ]
Mongoose: metamodels.findOne({ __t: 'DateModel' }) { fields: undefined }
{ created_at: Thu Apr 07 2016 10:24:08 GMT+1000 (AEST),
  __t: 'DateModel',
  __v: 0,
  value: Thu Apr 07 2016 10:24:08 GMT+1000 (AEST),
  _id: 5705a8a8443c0f74491bdec2 }

You can see that everything is being created in the metamodels collection assigned to the main model, however when referencing each "discriminator model" there is a __t field automatically created that includes the model name. This will be used later on reading the data back so mongoose knows which model and attached schema to apply when casting the objects.

Naturally since these all have their own schema, the standard validation rules apply. In addition any "instance methods" attached to the schema for each type also apply just as it would with any separate model.

Finally, that __t field is also applied with using one of the "discriminator models" for any other operation such as query or update. As shown in the last executed statement:

      DateModel.findOne().exec(function(err,doc) {
        console.log(doc);
        callback(err);
      });

And the actual call:

Mongoose: metamodels.findOne({ __t: 'DateModel' }) { fields: undefined }

That property value is included automatically to indicate the "type" and give a "virtual view" of the collection data just as if it only contained that particular type.

The real power actually lies in all objects being in the same collection and the ability of mongoose to assign the "class type" automatically on retrieval of data.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM