MongooseはネストされたフィールドのObjectIdタイプを自動的に変換できません


0. Intro


先日開発の楽屋で奇妙な現象が発見された.明らかに、mongoseアーキテクチャでは同じObjectIdタイプのフィールドであり、一部のフィールドはstringであり、他のフィールドはObjectIdであり、DBに格納されている.コードで原因が全く分からず、グーグル検索でも原因が見つからないので、一日中原因を探しています.正確な原因はまだ見つかっていないが、簡単な問題の状況と解決方法はここに書くことができるはずだ.

1.問題の状況


問題の状況は以下のように要約する.
mongoseアーキテクチャでnested fieldとして指定されているフィールドがObjectIdフィールドである場合、mongoseはhex stringを自動的にObjectIdに変換しません.
mongoseは基本的にアーキテクチャ上ObjectIdとして指定され、MongoDBがIDとして認識されるhex stringを入力すると、自動的にObjectIdタイプに変換されてデータベースに保存される(リファレンス).ただし、ObjectIdタイプのフィールドがネストされたフィールドであれば、ObjectIdタイプのデータを入力すれば問題ありませんが、hex stringを入力すると自動変換は動作しません.
{
  foo: {
    bar: "572bb8222b288919b68abf5b"
  },
  foobar: ObjectId("572bb8222b288919b68abf5b")
}
"572bb8222b288919b68abf5b" hex stringは確かに加わっているが、foobarではObjectIdに変換され、foo.barではそのままraw stringとして記憶されている.このように異なるストレージ方式では、クライアントから取得する際に異なるタイプで読み取りが行われるため、追跡が困難な問題が発生しやすい.

2.問題の再現


これがプロジェクト内の設定のためであることを理解するために、ローカルでMongoDBとMongooseサーバを簡単に設定しました.
import mongoose from 'mongoose'

const hexStr = '572bb8222b288919b68abf5b'
const { connect, disconnect, Schema, model } = mongoose

async function main() {
  await connect('mongodb://127.0.0.1:27017/test')

  const testSchema = new Schema({
    foo: {
      type: {
        bar: {
          type: Schema.Types.ObjectId,
          required: true
        }
      },
      required: true
    },
    foobar: {
      type: Schema.Types.ObjectId,
      required: true
    },
  })
  
  const Test = model('Test', testSchema)
  
  // 똑같은 hexStr 문자열을 넣어줌
  await Test.create({
    foo: {
      bar: hexStr
    },
    foobar: hexStr
  })

  await disconnect()
}

main().catch((err) => console.log(err))
前述したように、ネストされたフィールドfoo.barおよび通常のフィールドfoobarは、いずれもObjectIdのタイプとして指定される.上記のコードが実行されると、データベースは、foo.barおよびfoobarをフィールドとするドキュメントを生成し、終了する.

3.結果


Mongooseオプションではnested fieldに関する設定が設定されていないため、半分の人が事前にこれが現象だと予想していたので、同じ現象が再び発生することはもちろん知っています.しかし、このように再現された結果、NestedFieldも同様にObjectId種類でデータに入った.
{
  "_id": "ObjectId("624ee7997c9b7ed5a9366234")",
  "foo": {
    "bar": "ObjectId("572bb8222b288919b68abf5b")",
    "_id": "ObjectId("624ee7997c9b7ed5a9366235")"
  },
  "foobar": "ObjectId("572bb8222b288919b68abf5b")",
  "__v": 0
}
もしこれが選択肢の違いでなければ、何が違いますか?考えてみると、インストールされている猫王バージョンの違いかもしれないと思います.上のテストコードのmongoseバージョンは6.2.10で、プロジェクト内のmongoseバージョンは5.9.3です.そこでmongoseバージョンを下げてコードを再実行しようとしました.
{ 
  "_id" : ObjectId("624ee91a03b8168ef8edbc34"), 
  "foo" : { 
    "bar" : "572bb8222b288919b68abf5b" 
  }, 
  "foobar" : ObjectId("572bb8222b288919b68abf5b"), 
  "__v" : 0 
}
あ….その結果、猫王バージョンによって動作が異なります.[email protected]万の話題かどうかはわかりませんが、設定に関する話題ではないようで、アップロードプロジェクトのmongoseバージョンまでは、これはもう抱えて歩かなければならない問題になっていました.

4.便宜の策


何らかの理由でmongoseを更新できない場合や、攻守が大きい場合は、hex stringを個別にパッケージする関数を定義できます.
import mongoose from 'mongoose'

function convertToObjectId(id: string | mongoose.Types.ObjectId) {
  if (typeof id === 'string') {
    return mongoose.Types.ObjectId(id)
  }
  return id
}

// 새 document 생성 시 ObjectId를 wrapping
await Test.create({
  foo: {
    bar: convertToObjectId(hexStr)
  },
  foobar: convertToObjectId(hexStr)
})