MongoDB 컬렉션에 문서를 업데이트하는 코드를 넣고 테스트를 하다가 업데이트하는 필드 중 하나에 뜬금없이 null값이 들어가 있는 이상한 현상을 발견했습니다. 테스트 코드를 만들어 확인해보니, 문서의 특정 필드가 undefined인채로 FindOneAndUpdate 함수가 호출되면 그 필드는 null로 변환되어 저장되고 있습니다. 지금까지 undefined 값을 가진 필드는 MongoDB에 입력되기 전에 무시되는 걸로 알고 있었는데 말이죠.

좀 더 테스트를 해보고 내린 결론은 save()를 할 때는 undefined가 무시되고, update()를 하면 undefinednull로 치환되어 저장된다는 것입니다. 이 것이 update 연산자로 연산을 하기 위해서 어쩔 수 없이 존재하는 문제인지, 아니면 다른 이유가 있는것인지까지는 확인해보지 못했습니다. 하지만 필드를 명시적으로 null로 업데이트하는 것이 아닌데도 불구하고, undefined 값이 필드에 저장되는 것은 조금 이상합니다.

이 이슈에서 기억해두실 점은, ‘mongoose를 사용하면서 특정 필드의 값이 채워지지 않은채로 입력값이 들어오는 경우 null 필드가 만들어질 수 있다’입니다

Mongoose issue 페이지에 가서 보니 프로젝트 오너가 undefined를 지원하지 않는 js-bson 때문에, 내부에서 null로 자동 변환된다고 설명하네요. 이것은 아마도 mongoose가 아니라 mongoDB 쪽의 문제로 보입니다. (관련 페이지: mongodb/js-bson#134). 질문자도 저와 똑같은 궁금증으로 질문을 올린 것 같은데, 오너는 질문의 의도를 정확하게 파악하지 못한 것 같습니다.

아무리 내부적인 한계가 있다지만, save()를 할 때는 알아서 제거해주던 것을, update()를 할 때는 그대로 둔다는 건 API 인터페이스의 일관성이 없는 문제로 보입니다. 특히, save()는 mongoose 프레임워크에서 지원하는 것이니 MongoDB 탓만 할게 아닐 것 같고요.

아래는 테스트 코드입니다:

const mongoose = require('mongoose')

const UndefinedTest = mongoose.model('UndefinedTest', new mongoose.Schema({
  name: String,
  job: String,
  family: {
    name: String,
    age: Number
  },
}));

(() => {
  mongoose.connect('mongodb://localhost/dev', {
    useNewUrlParser: true
  }, async (err) => {
    const db = mongoose.connection

    await UndefinedTest.deleteMany({})

    const saveData = {
      name: 'Reid',
      job: undefined,
      family: {
        name: undefined,
        age: 17
      }
    }
    await new UndefinedTest(saveData).save()
    const saveDoc = await UndefinedTest.find({ name: 'Reid' }).lean()
    console.log('Create a document with undefined fiels')
    console.table(saveDoc)

    const updateData = {
      job: undefined,
      family: {
        name: undefined,
        age: 27
      }
    }
    await UndefinedTest.findOneAndUpdate({ name: 'Reid' }, { $set: updateData })
    const updateDoc = await UndefinedTest.find({ name: 'Reid' }).lean()
    console.log('Update the document with undefined fiels')
    console.table(updateDoc)

    await db.close()
  })
})()
Create a document with undefined fiels
┌─────────┬──────────────────────────┬────────┬─────────────┬─────┐
│ (index) │           _id            │  name  │   family    │ __v │
├─────────┼──────────────────────────┼────────┼─────────────┼─────┤
│    0    │ 5c947208fb19935055d655cf │ 'Reid' │ { age: 17 } │  0  │
└─────────┴──────────────────────────┴────────┴─────────────┴─────┘

Update the document with undefined fiels
┌─────────┬──────────────────────────┬────────┬─────────────────────────┬─────┬──────┐
│ (index) │           _id            │  name  │         family          │ __v │ job  │
├─────────┼──────────────────────────┼────────┼─────────────────────────┼─────┼──────┤
│    0    │ 5c947208fb19935055d655cf │ 'Reid' │ { name: null, age: 27 } │  0  │ null │
└─────────┴──────────────────────────┴────────┴─────────────────────────┴─────┴──────┘