Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nullish coalescing assignment operator does not work as expected with mongoose. #14944

Closed
2 tasks done
CodingMeSwiftly opened this issue Oct 8, 2024 · 3 comments · Fixed by #14972
Closed
2 tasks done
Labels
docs This issue is due to a mistake or omission in the mongoosejs.com documentation
Milestone

Comments

@CodingMeSwiftly
Copy link

CodingMeSwiftly commented Oct 8, 2024

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Mongoose version

7.2.2

Node.js version

16.20.2

MongoDB server version

7.0.2

Typescript version (if applicable)

5.5.4

Description

When setting a field in an optional subdocument via nullish coalescing assignment, the incoming value is not set on the subdocument.

Usually, the nullish coalescing operator allows for patterns like this:

const obj: { nested?: { a?: number } } = {}

(obj.nested ??= {}).a = 5

console.log(obj.nested.a)
// 5

However, this does not work when used with a mongoose document:

(doc.nested ??= {}).a = 5

console.log(doc.nested.a)
// undefined

The reason is the fact that mongoose wraps the 'drive-by' assignment of subdocument in a SingleNested object. Nullish coalescing assignment returns the right hand operand if the left hand operand is nullish. Setting a field on that object (POJO) will not be picked up by mongoose, because doc.nested does not reference the POJO but rather the new object mongoose created under the hood.

Steps to Reproduce

import mongoose from 'mongoose'
const { Schema } = mongoose

const schema = new Schema({
    nested: {
        a: Number
    }
})

const Model = mongoose.model('Test', schema)

const doc = new Model()

(doc.nested ??= {}).a = 5

console.log(doc.nested.a)
// undefined

The issue becomes even more apparent when looked at without the property assignment:

const doc = new Model()

const nested: (typeof doc)['nested'] = {}

const x = doc.nested ??= nested

console.log(x === nested)
// false <- Should be true!

Expected Behavior

This is somewhat of an edge case as the nullish coalescing assignment operator might be considered quite exotic.
However, it's a big gotcha for people who don't know that mongoose routinely wraps and transforms objects under the hood.

Mongoose should not change how js operators function.
At the very least, the documentation should explicitly point out the behaviour.

@CodingMeSwiftly
Copy link
Author

The same issue can also be observed for the other coalescing assignment operators Logical OR and Logical AND.

@vkarpov15 vkarpov15 added this to the 8.7.2 milestone Oct 11, 2024
@vkarpov15 vkarpov15 added the has repro script There is a repro script, the Mongoose devs need to confirm that it reproduces the issue label Oct 11, 2024
@vkarpov15
Copy link
Collaborator

Looks like this affects subdocs, not nested paths.

const mongoose = require('mongoose');

const { Schema } = mongoose;

const schema = new Schema({
    nested: {
        a: Number
    },
    subdoc: new Schema({ b: Number })
});

const Model = mongoose.model('Test', schema);

const doc = new Model();

(doc.nested ??= {}).a = 5;
(doc.subdoc ??= {}).b = 6;

console.log(doc.nested.a); // 5
console.log(doc.subdoc.b); // undefined
console.log(doc.subdoc); // Only _id

We're investigating why this is happening

@vkarpov15
Copy link
Collaborator

We're going to have to add this to our docs. The issue is that, if subdoc is nullish, (doc.subdoc ??= {}) evaluates to an empty POJO {},not the new value of doc.subdoc.

The preferred way to do this sort of safe property assignment in Mongoose is doc.set('subdoc.b', 6), which will automatically create subdoc as well.

@vkarpov15 vkarpov15 added docs This issue is due to a mistake or omission in the mongoosejs.com documentation and removed has repro script There is a repro script, the Mongoose devs need to confirm that it reproduces the issue labels Oct 11, 2024
vkarpov15 added a commit that referenced this issue Oct 17, 2024
…luding warning about nullish coalescing assignment

Fix #14944
vkarpov15 added a commit that referenced this issue Oct 17, 2024
docs(documents): add section on setting deeply nested properties, including warning about nullish coalescing assignment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs This issue is due to a mistake or omission in the mongoosejs.com documentation
Projects
None yet
2 participants