クイック修正
このエラーは、address.cityのようなネストされたフィールドを更新しようとした際、データベース内の親フィールド(address)が明示的にnullに設定されている場合に発生します。MongoDBはnull値を自動的にオブジェクトに変換することはできません。
最も手っ取り早い修正方法は、親オブジェクト全体を上書きすることです。そのフィールドに元々入っていた内容を置き換えても問題ない場合は、この方法を使用してください。
db.users.updateOne(
{ _id: ObjectId("64f1a2b3c4d5e6f7a8b9c0d1") },
{ $set: { "address": { "city": "New York" } } }
)
address内の他の既存データを保持する必要がある場合は、より慎重なアプローチが必要です。なぜこの現象が起こるのか、そして安全に対処する方法を見ていきましょう。
なぜこのエラーが発生するのか?
MongoDBのドット記法は強力ですが、制約があります。ドット記法でたどることができるのはオブジェクトのみです。null、文字列、ブール値の中を探索することはできません。
例えば、ドキュメントが以下のようになっているとします。
{
"_id": 101,
"name": "Alice Smith",
"address": null
}
{ $set: { "address.city": "Seattle" } }を実行すると、MongoDBはaddressを確認しますが、そこで行き止まりになります。nullはプリミティブ値であるため、MongoDBはそこに子プロパティを付加する方法を判断できません。その結果、処理を即座に停止し、MongoServerErrorをスローします。
興味深いことに、もしaddressフィールドが完全に存在しなかった場合、MongoDBは自動的にオブジェクト構造を作成してくれます。このエラーは、フィールドが存在しているものの、型が正しくないために発生します。
3つの解決方法
1. 親フィールドを上書きする(直接的な方法)
フィールドが確実にnullである場合、またはオブジェクトをリセットしても構わない場合にこの方法を使用します。複雑なロジックを必要としないため、最もパフォーマンスの高い方法です。
// nullを新しいオブジェクトで置き換えます
db.collection.updateOne(
{ _id: 101 },
{ $set: { address: { city: "Austin" } } }
)
2. 事前にクリーンアップする(一括修正に最適)
null値を持つドキュメントが数千個ある場合もあります。そのような時は、まずクリーンアップスクリプトを実行して、すべてのnullの値を空のオブジェクトに変換します。オブジェクトに変換されれば、通常のドット記法による更新が正常に動作するようになります。
// ステップ1: nullを空のオブジェクトに変換
db.collection.updateMany(
{ address: null },
{ $set: { address: {} } }
);
// ステップ2: これで通常の更新が可能になります
db.collection.updateOne(
{ _id: 101 },
{ $set: { "address.city": "Austin" } }
);
3. スマートな更新(MongoDB 4.2以降)
最新のMongoDBバージョンでは、更新処理内での集計パイプライン(aggregation pipeline)の使用をサポートしています。これは最も堅牢な方法です。実行時にデータ型をチェックし、マージするか置換するかを動的に決定します。
db.collection.updateOne(
{ _id: 101 },
[
{
$set: {
address: {
$cond: {
if: { $eq: [{ $type: "$address" }, "object"] },
then: { $mergeObjects: ["$address", { city: "Austin" }] },
else: { city: "Austin" }
}
}
}
}
]
)
このスクリプトは、「addressはすでにオブジェクトか?」を確認します。もしそうなら、既存のデータに新しいcityをマージします。そうでなければ(nullや文字列の場合)、新しいオブジェクトを作成します。
変更の確認方法
ドキュメントを直接クエリして、結果を確認しましょう。シェルで以下を実行します。
db.collection.find({ _id: 101 }).pretty()
以下のような、整ったネスト構造が表示されれば成功です。
{
"_id": 101,
"address": {
"city": "Austin"
}
}
より良いスキーマ設計の習慣
スキーマ設計を少し工夫するだけで、この問題を完全に回避できます。ネストされたオブジェクトをnullで初期化しないでください。ユーザーがまだ住所を入力していない場合は、ドキュメントからaddressフィールド自体を除外しておきます。MongoDBは、更新処理において、明示的なnullよりも「フィールドが存在しない状態」をはるかに柔軟に処理できます。

