何が起きたのか
ドキュメントを保存しようとしたところ、MongoDBが以下のエラーをスローしました:
MongoServerError: The dotted field 'user.name' in 'data.user.name' is not valid for storage.
原因はMongoDBの基本的なルールにあります:ドット記法(parent.child)はMongoDBがネストされたドキュメントを辿るための構文です。フィールドのキー自体にドットが含まれていると、MongoDBはネストされたパスとリテラルのキー名を区別できなくなるため、保存を完全に拒否します。
ほとんどのケースでは、サニタイズされていないユーザー入力、CSV/JSONインポート、またはサードパーティAPIのレスポンスが原因です。コード上ではキーが正常に見えても、どこかでドットが紛れ込んでいます。
問題の再現
Node.jsでの最小限の再現例です:
// Node.js — mongodb driver
const doc = {
data: {
'user.name': 'Alice' // dot inside the key!
}
};
await collection.insertOne(doc);
// → MongoServerError: The dotted field 'user.name' in 'data.user.name'
// is not valid for storage.
insertOneだけの問題ではありません。updateOne、findOneAndUpdate、replaceOneなど、ドットを含むキーを持つドキュメントを渡すすべての書き込み操作で、どれだけ深くネストされていても同じエラーが発生します。
ドットの場所を特定する
修正方法を選ぶ前に、問題のあるキーをすべて洗い出しましょう。小さな再帰ヘルパー関数を使えば数秒で確認できます:
// JavaScript — find all dotted keys in an object
function findDottedKeys(obj, path = '') {
for (const key of Object.keys(obj)) {
const fullPath = path ? `${path}.${key}` : key;
if (key.includes('.')) {
console.warn('Dotted key found:', fullPath);
}
if (obj[key] !== null && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
findDottedKeys(obj[key], fullPath);
}
}
}
findDottedKeys(doc);
// Dotted key found: data.user.name
デバッグ中は保存前にペイロードに対してこの関数を実行してください。問題の範囲(1フィールドか50フィールドか)を把握したら、適切な修正方法を選択しましょう。
修正1 — キーのドットを安全な文字に置換する
MongoDBへの書き込み前に、キー名のドットをアンダースコア(または別の区切り文字)に置換します:
// JavaScript — recursively sanitize keys
function sanitizeKeys(obj) {
if (Array.isArray(obj)) {
return obj.map(sanitizeKeys);
}
if (obj !== null && typeof obj === 'object') {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [
k.replace(/\./g, '_'), // dot → underscore
sanitizeKeys(v)
])
);
}
return obj;
}
const safe = sanitizeKeys(doc);
await collection.insertOne(safe);
実際のキー名と衝突しない区切り文字を選んでください。よく使われるのは_、中点·(U+00B7)、またはUnicodeエスケープの\u2024です。どれを選んだとしても記録しておきましょう — データを読み返すときに逆変換が必要になります。
修正2 — 適切なネストドキュメント構造に変更する
user.nameのようなドット付きキーが、もともとネストされたフィールドとして意図されていた場合もあります。その場合は、ネスト構造を明示的にするだけです:
// Before (wrong)
const bad = { data: { 'user.name': 'Alice' } };
// After (correct nested structure)
const good = { data: { user: { name: 'Alice' } } };
await collection.insertOne(good);
これはソースデータの形式が単に不適切だった場合に最もクリーンな解決策です。おまけとして、クエリもより自然になります — { 'data.user.name': 'Alice' }がMongoDBの意図通りに機能します。
修正3 — 動的キーをキーと値のペアの配列として保存する
フォームの送信、アナリティクスイベント、ユーザー定義の設定など、本当に予測不能なフィールド名がある場合はどうでしょうか?MongoDBのキールールと戦うのではなく、配列を使用しましょう:
// Instead of: { 'user.name': 'Alice', 'user.email': 'a@b.com' }
// Store as:
const doc = {
attributes: [
{ key: 'user.name', value: 'Alice' },
{ key: 'user.email', value: 'a@b.com' }
]
};
await collection.insertOne(doc);
クエリはより冗長になります({ attributes: { $elemMatch: { key: 'user.name', value: 'Alice' } } })が、外部ソースからどんなキーが来てもスキーマは有効なままです。高カーディナリティの属性セットではインデックスの爆発も回避できます。
Python / pymongo
同じエラー、同じ根本原因です:
# pymongo
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/')
col = client.mydb.mycollection
doc = {'data': {'user.name': 'Alice'}}
col.insert_one(doc)
# raises: pymongo.errors.WriteError:
# The dotted field 'user.name' in 'data.user.name' is not valid for storage.
Pythonでの修正方法はJavaScriptと同様です:
def sanitize_keys(obj):
if isinstance(obj, list):
return [sanitize_keys(i) for i in obj]
if isinstance(obj, dict):
return {
k.replace('.', '_'): sanitize_keys(v)
for k, v in obj.items()
}
return obj
safe_doc = sanitize_keys(doc)
col.insert_one(safe_doc)
Mongoose(Node.js ODM)
MixedまたはSchema.Types.Mixedフィールドを持つMongooseを使用していますか?すべての書き込みで自動的にサニタイズが行われるよう、pre-saveフックを追加しましょう:
schema.pre('save', function (next) {
if (this.data) {
this.data = sanitizeKeys(this.data);
this.markModified('data');
}
next();
});
修正の確認
3つの簡単なチェックですべて正常に動作していることを確認できます:
// 1. Insert a test document
const result = await collection.insertOne(sanitizeKeys(testDoc));
console.log('Inserted ID:', result.insertedId);
// 2. Read it back
const stored = await collection.findOne({ _id: result.insertedId });
console.log(JSON.stringify(stored, null, 2));
// Keys should show underscores (or nested structure), no dots
// 3. Confirm no dotted keys remain
findDottedKeys(stored); // Should print nothing
挿入が成功し、保存されたドキュメントが正しく見え、findDottedKeysが何も出力しなければ完了です。
上流での防止策
- 境界でバリデーションする — データベース層に到達する前にドット付きキーを拒否またはサニタイズしましょう。JoiやZodなどのスキーマバリデーターはAPIエントリポイントでキーフォーマットルールを強制できます。ここでキャッチするのが最もコストが低い方法です。
- ユーザー定義のキーをそのまま公開しない — ユーザーが独自のフィールド名を付けられる場合は、任意の文字列をドキュメントキーにするのではなく、修正3(
{ key, value }配列パターン)を経由させましょう。 - MongoDB 5.0以降ではこの制限が一部緩和されました —
$setFieldなどの演算子を使用した集計コンテキストでは、フィールド名にドットとドル記号が許可されています。ただし、通常のinsert/updateではドライバーレベルで依然として拒否されます。アプリケーション側でサニタイズしましょう — バージョン固有のサーバー動作に依存しないでください。

