$set operator missing _id on new embedded object

Context
Whenever a new document is created without an embedded object the update with the $set operator does not generate the _id. For this discussion I’ve used the nodejs driver. Consider the following JSON as an example of the bike model

{
    "businessId": "Bike1",
    "name": "Bert",
    "properties": {
        "type": "Electric",
        "weight": "1337mg"
    }
}

Suppose I create a new record without specifying the properties like:

{
    "businessId": "Bike2",
    "name": "Ernie",
}

This results in a new record.

{
    "_id": "6453cb0914251323e1b9f40e",
    "businessId": "Bike2",
    "name": "Ernie",
    "__v": 0
}

At this point the properties are not known. Later on the properties object might be added to the document. Now I want to add the type and weight of Bike2. For this I use a findOneAndUpdate.

const doc = await bikeModel.findOneAndUpdate({ businessId: businessid }, { $set: { 'properties.type': 'Gas', 'properties.weight': '1kg' } }, { returnDocument: "after", runValidators: true });

This findOneAndUpdate results in:

{
    "_id": "6453cb0914251323e1b9f40e",
    "businessId": "Bike2",
    "name": "Ernie",
    "__v": 0,
    "properties": {
        "type": "Gas",
        "weight": "1kg",
        "_id": "6453d38e14251323e1b9f415"
    }
}

Note that the returnDocument contains an _id in the properties. Whenever I retrieve this record the _id does not exist on my document. Neither does the _id exist in the when viewing this record in Compass. See below the retrieved record

{
    "items": [
        {
            "_id": "6453cb0914251323e1b9f40e",
            "businessId": "Bike2",
            "name": "Ernie",
            "__v": 0,
            "properties": {
                "type": "Gas",
                "weight": "1kg"
            }
        }
    ],
    "size": 1,
    "hasMore": false
}

Whenever I add the properties to the initial creation of the record it does work. Instead of the previous create I now add an empty object. Suppose I create a new record without specifying the properties like:

{
    "businessId": "Bike2",
    "name": "Ernie",
    "properties": { }
}

This results in a record with _id on the properties object. With this situation the $set operation works as expected. It results in the creation of a type and weight on the existing properties object.

{
    "_id": "6453cb0914251323e1b9f40e",
    "businessId": "Bike2",
    "name": "Ernie",
    "properties": {
        "_id": "6454bfdd9bb9cb2afd60b6e0"
    },
    "__v": 0
}

According to the $set documentation:

If the field does not exist, $set will add a new field with the specified value, provided that the new field does not violate a type constraint. If you specify a dotted path for a non-existent field, $set will create the embedded documents as needed to fulfill the dotted path to the field.

Questions
To me it’s unclear why the $set operator is not able to generate a properties object with _id. Perhaps I misinterpreted the documentation.

  • Is this expected behaviour of the $set operator?
  • How should I update the properties of a bike document? (possibly without having to define an empty properties object upon creation)

My apologies for the long description.
Thank you in advance.

The following seems to indicate that you are using Mongoose.

1 - The presence of __v field, which is not a feature of pure MongoDB.

2 - Using model rather than collection for your CRUD

3 - The presence of an automatic _id in sub-document

So you seem to be confused between Mongoose layer and native MongoDB because despite using Mongoose’s API, you link to the native MongoDB’s $set documentation.

Just to be clear, pure MongoDB has no automatic concept of __v and no automatic concept of _id inside embedded document. The only automatic concept in pure MongoDB is _id in top level document.

Note that what ever automatic field generation or update performed by Mongoose will not be done when using Compass. However, the fields generated and updated by Mongoose should be visible.

I have tagged your post with mongoose to attract attention to mongoose user.