Mongoose optional nested property - check for null/undefined?
Asked Answered
C

4

7

I'm trying to have an "optional" nested document in one of my models. Here's a sample schema

var ThingSchema = mongoose.Schema({
  name: String,
  info: {
      'date' : { type: Date },
      'code' : { type: String },
      'details' : { type: Object }
  }
})
var Thing = mongoose.model('Thing', ThingSchema);

In this case, I want the "info" property to be possibly null (not required), and will have application logic that checks if the info is null/undefined.

Unfortunately, even when the 'info' property is undefined in the document in mongo, mongoose still gives me back an object for the 'info' field within the retrieved model.

For example, When I run this code, the 'info' object is populated with an (empty) object.

var thingOne = new Thing({
  name: "First Thing"
})

thingOne.save(function(err, savedThing) {
    if (savedThing.info) {
        //This runs, even though the 'info' property 
        //of the document in mongo is undefined
        console.log("Has info!");
    }
    else console.log("No info!");
})

I tried defining ThingSchema like the following

var ThingSchema = mongoose.Schema({
  name: String,
  info: {
    type: {
      'date' : { type: Date },
      'code' : { type: String },
      'details' : { type: Object }
    },
    required: false

  }
})

Which allows me to have a undefined "info" property retrieved from the database, but doing this allows me to break the "info" schema and add random properties like

savedThing.info.somePropertyThatDoesNotExistInTheSchema = "this shouldn't be here";

Which will then get persisted to the database. Additionally, if the "info" property contains any further nested documents, updating them doesn't mark the Thing model as dirty, which means I have to manually mark the path as modified ( via thing.markModified(path) )

Is there some way to accomplish what I'm trying to do? Are there any best practices for this? Am I just using mongoose schemas completely incorrectly?

I need to either

  1. Retrieve a null "info" property for the Thing schema
  2. Have some way to test if the "info" property is actually null in mongo even though mongoose gives it a value when it is retrieved.
Crotchety answered 8/1, 2015 at 23:13 Comment(1)
What if you use your first Schema and check with hasOwnProperty if your return item had 'info'? This doesn't mean it returns null but at least you can check whether there is more information. If this is not an option, please tell me more where you check for null/undefined so I can find another way.Baerl
B
0

Mongoose will cast any returned document to the schema used to define the model.

With regards to arbitrary paths being persisted to the database, you could use strict to throw an error rather than dropping it silently (which it should be doing by default).

Bobbe answered 2/4, 2015 at 21:33 Comment(0)
W
0

There is connection option ignoreUndefined in native mongodb driver, which could solve this issue. https://mongodb.github.io/node-mongodb-native/2.1/reference/connecting/connection-settings/

Wooded answered 16/1, 2020 at 20:8 Comment(0)
A
0

Try using

const Thing = mongoose.model('Thing', ThingSchema)

Thing.create({ name: "First Thing" })

instead of

var thingOne = new Thing({ name: "First Thing" })
thingOne.save(...)

Here's how to use it

mongoosejs.com/...model_Model.create

Aiglet answered 27/10, 2020 at 13:54 Comment(0)
G
0

Quoting from your post - "optional nested document", this term needs to be looked little more closely.

Speaking in mongoose terms, there are two things related to your "optional nested documents". They are Subdocuments and Nested Paths. These two though appears the same, it works internally in different ways.

While a Subdocument optionally can be undefined, a Nested path will never be undefined.

This is what happens in your case. In your it is a Nested path, not a Subdocument. You need a Subdocument to meet your objective.

Please see the examples quoted from mongoose documentation.

Below are two schemas: one with child as a subdocument, and one with child as a nested path.

// Subdocument

const subdocumentSchema = new mongoose.Schema(
      {child: new mongoose.Schema({ name: String, age: Number })});
const Subdoc = mongoose.model('Subdoc', subdocumentSchema);

// Nested path

const nestedSchema = new mongoose.Schema({
child: { name: String, age: Number }});
const Nested = mongoose.model('Nested', nestedSchema);

These two schemas look similar, and the documents in MongoDB will have the same structure with both schemas. But there are a few Mongoose-specific differences:

First, instances of Nested never have child === undefined. You can always set subproperties of child, even if you don't set the child property. But instances of Subdoc can have child === undefined.

const doc1 = new Subdoc({});
doc1.child === undefined; // true
doc1.child.name = 'test'; // Throws TypeError: cannot read property...

const doc2 = new Nested({});
doc2.child === undefined; // false
console.log(doc2.child); // Prints 'MongooseDocument { undefined }'
doc2.child.name = 'test'; // Works

For more: Subdocuments versus Nested Paths

Gainsay answered 30/3 at 8:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.