キー名のMongoDBドット(。)


93

mongoではドット(。)またはドル記号($)の付いたキーの挿入が許可されていないようですが、mongoimportツールを使用してドットを含むJSONファイルをインポートすると正常に機能しました。ドライバーは、その要素を挿入しようとしていることに不満を持っています。

これは、データベースでのドキュメントの外観です。

{
    "_id": {
        "$oid": "..."
    },
    "make": "saab",
    "models": {
        "9.7x": [
            2007,
            2008,
            2009,
            2010
        ]
    }
}

私はこれをすべて間違って行っており、外部データ(つまりモデル)でそのようなハッシュマップを使用するべきではありませんか、それともどういうわけかドットをエスケープできますか?たぶん私はJavascriptのように考えすぎています。


見る価値がある、npmjs.com / package / mongo
Sam

回答:


85

MongoDBは、ドットが含まれるキーをサポートしていないため、JSONファイルをインポートする前に、JSONファイルを前処理して削除/置換する必要があります。そうしないと、あらゆる種類の問題に備えることができます。

この問題に対する標準的な回避策はありません。最善のアプローチは、状況の詳細に依存しすぎます。ただし、JSONの再構築がおそらく一度限りのコストになるという不便さを永続的に支払うことになるため、可能であれば、キーエンコーダー/デコーダーのアプローチは避けたいと思います。


1
標準的な方法はないと思います。最善のアプローチは、状況の詳細に依存しすぎます。ただし、JSONの再構築がおそらく一度限りのコストになるという不便さを永続的に支払うことになるため、可能であれば、キーエンコーダー/デコーダーのアプローチは避けたいと思います。
JohnnyHK 2012

8
再びこの状況に遭遇しました。これは、制御できてクエリが必要になることが多いアプリキー名ではあまり発生しないようですが、制御できないネストされたデータ構造のユーザー指定データでは発生しますが、(a)Mongoに保存したい、(b)これが発生する可能性のある特定のフィールド(modelsここなど)がわかっている、および(c)Mongoでキー名でクエリする必要がない。したがって、私が決めたパターンはJSON.stringify、保存時にこのフィールドに、取得時に 'JSON.parse`になります。
プロトタイプ

16
必要に応じて、この問題を回避するために{check_keys:false}オプションを提供できます。
Tzury Bar Yochay 2013

5
@TzuryBarYochay OMGは、北西航路に相当するMongoDBを見つけました。これが受け入れられる答えだと思います。
プロトタイプ

2
@emarel db.collection_foo.update({this: "that"}、{$ set:{a: "b"}}、{check_keys:false})
Tzury Bar Yochay 2016年

22

他の回答で述べたように、MongoDBでは、フィールド名の制限により、$または.文字をマップキーとして使用できません。ただし、ドル記号演算子で説明したように、この制限をエスケープしても、そのようなキーを使用してドキュメントを挿入できなくなるわけではなく、更新やクエリができなくなるだけです。

単に置き換えるの問題.[dot]U+FF0E(このページの上に別の場所で述べたように)は、ユーザーが合法的にキー保存したいときに何が起こるか、である[dot]かをU+FF0E

Fantomの afMorphiaドライバー採用するアプローチは、Javaと同様のユニコードエスケープシーケンスを使用することですが、エスケープ文字が最初にエスケープされるようにします。本質的に、次の文字列置換が行われます(*):

\  -->  \\
$  -->  \u0024
.  -->  \u002e

その後、マップキーがMongoDBから読み取られるときに、逆の置換が行われます。

またはファントムコード:

Str encodeKey(Str key) {
    return key.replace("\\", "\\\\").replace("\$", "\\u0024").replace(".", "\\u002e")
}

Str decodeKey(Str key) {
    return key.replace("\\u002e", ".").replace("\\u0024", "\$").replace("\\\\", "\\")
}

ユーザーがそのような変換を認識する必要があるのは、そのようなキーのクエリを作成するときだけです。

dotted.property.names構成の目的でデータベースに保存するのが一般的であることを考えると、このアプローチは、そのようなすべてのマップキーを単に禁止するよりも望ましいと思います。

(*)afMorphiaは、JavaのUnicodeエスケープ構文で説明されているように、実際には完全で適切なUnicodeエスケープルールを実行しますが、説明されている置換シーケンスも同様に機能します。


//g最初だけでなく、すべてのオカレンスを置き換えるために使用する必要があります。また、Martin Konecnyの回答のように、全幅に相当するものを使用することは良い考えのようです。最後に、エンコードには1つの円記号で十分です。key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
cw '

1
@ cw'-コードはJavaのような構文であるため、replaceは実際にはすべてのオカレンスを置き換えます。バックスラッシュをエスケープするには、ダブルバックスラッシュが必要です。また、すべてのケースを確実にカバーするために、何らかの形のエスケープを導入する必要があります。誰かが、いつか、実際にのキーを必要とする場合がありU+FF04ます。
Steve Eynon 2016

2
結局のところ、Mongodbは最新バージョンでドットとドルをサポートしています。参照: - stackoverflow.com/a/57106679/3515086
Abhidemon

18

Mongoのドキュメントは、などの不正な文字を置き換えることを提案$し、.そのユニコード同等で。

このような状況では、キーは予約済みの$とを置き換える必要があります。文字。任意の文字で十分ですが、Unicodeの全角に相当するU + FF04(つまり「$」)とU + FF0E(つまり「。」)の使用を検討してください。


74
これは、将来の大規模なデバッグの頭痛の種のレシピのように聞こえます。
誰も

2
@ AndrewMedico、@ tamlyn-ドキュメントは次のような意味だと思いますdb.test.insert({"field\uff0ename": "test"})
P. Myer Nore

4
-1 A.それはひどい考えです-誰かが実際にそれらのUnicode文字をキーとして使用しようとしている場合はどうなりますか?次に、システムに何を知っているのかを判断するサイレントエラーが発生します。そのようなあいまいなエスケープ方法を使用しないでください。B. mongoのドキュメントでは、おそらく誰かがそのひどいアイデアに気付いたためだとは言われていません
BT

7
@SergioTulentsev推奨事項を削除してもらいました:)github.com/mongodb/docs/commit/…–
BT

2
@BT:帽子のヒント、サー:)
Sergio Tulentsev 2016年

15

MongoDBの最新の安定バージョン(v3.6.1)は、キーまたはフィールド名のドット(。)をサポートするようになりました。

フィールド名にドット(。)とドル($)文字を含めることができるようになりました


10
サーバーが現在それをサポートしている場合でも、ドライバーはキーの$とドットをチェックし、それらを受け入れません。したがって、Mongoは理論的にはドットとドル文字のみをサポートします。実際には、これはまだ使用できません:(
JMax 2018

古いクライアントまたは互換性のないクライアントを使用している可能性があります。私はこれを本番サーバーで汗をかくことなく使用しています。NodeJSとJavaクライアントを確認しました。
h4ck3d 2018年

Javaでは明らかに機能しません!次のコマンドを試してください:mongoClient.getDatabase("mydb").getCollection("test").insertOne(new Document("value", new Document("key.with.dots", "value").append("$dollar", "value")));mongodb-driver.3.6.3およびMongoDB3.6.3の使用に失敗します。
JMax 2018年

1
確かに、私はセットアップmongodb-4.1.1とを試してみましたpymongo-3.7.1.robomongoを使用してキーを含むドキュメントを追加できますが、からは追加できませんpymongo。これでInvalidDocument: key '1.1' must not contain '.'修正されていればよかったのですが...
学習は混乱し

mongodbサーバー4.0.9とJavaドライバー3.10.2で試しましたが、キー名にドットを使用できません。それは...それが動作robomongo使用していることをしようとするのは奇妙だ
xyzt

12

私が実装したばかりで本当に満足しているソリューションには、キーの名前と値を2つの別々のフィールドに分割することが含まれます。このようにして、文字をまったく同じに保つことができ、それらの構文解析の悪夢について心配する必要はありません。ドキュメントは次のようになります。

{
    ...
    keyName: "domain.com",
    keyValue: "unregistered",
    ...
}

keyNamefindフィールドkeyValueフィールドでを実行するだけで、これを簡単にクエリできます。

したがって、代わりに:

 db.collection.find({"domain.com":"unregistered"})

これは実際には期待どおりに機能しないため、次のコマンドを実行します。

db.collection.find({keyName:"domain.com", keyValue:"unregistered"})

期待されるドキュメントが返されます。


どうやってやったの?同じケースで私を助けてくれませんか。
プロファイラー

クエリの例を追加しました。それは役に立ちますか?
スティーブ

10

値の代わりにキーでハッシュを使用してみて、その値をJSON値に格納することができます。

var crypto = require("crypto");   

function md5(value) {
    return crypto.createHash('md5').update( String(value) ).digest('hex');
}

var data = {
    "_id": {
        "$oid": "..."
    },
    "make": "saab",
    "models": {}
}

var version = "9.7x";

data.models[ md5(version) ] = {
    "version": version,
    "years" : [
        2007,
        2008,
        2009,
        2010
    ]
}

その後、ハッシュを使用してモデルにアクセスします。

var version = "9.7x";
collection.find( { _id : ...}, function(e, data ) {
    var models = data.models[ md5(version) ];
}

1
私はこれが好きで、一方向ハッシュを使用したクリーンなソリューションであり、内部での動作と非常によく似ています。
Michael Yagudaev 2014

3
ハッシュをキーとして使用する場合の問題は、ハッシュが一意であることが保証されておらず、頻繁に衝突が発生することです。さらに、マップにアクセスするたびに暗号化ハッシュを計算することは、私にとって最適なソリューションとは思えません。
Steve Eynon 2015

2
ピリオドを特殊文字またはシーケンスに置き換えるよりも、これが優れているのはなぜですか?
B七

文字列をbase64に変換する方がはるかに優れています。

8

現在サポートされています

MongoDb 3.6以降では、フィールド名でドットドルの両方がサポートされています。以下のJIRAを参照してください。 https //jira.mongodb.org/browse/JAVA-2810

Mongodbを3.6以降にアップグレードするのが最善の方法のようです。


これがここでの最良の答えです。:1
hello_abhishek

3
3.6はそれらを保存できます、はい、しかしそれはまだサポートされておらず、ドライバーエラーをスローし、クエリ/更新を壊す可能性があります:制限: "MongoDBクエリ言語は、フィールド名にこれらの文字が含まれるドキュメントに対して常に意味のあるクエリを表現できるとは限りません(SERVER-を参照) 30575)クエリ言語でサポートが追加されるまで、フィールド名での$と。の使用は推奨れておらず、公式のMongoDBドライバーではサポートされていません。」
JeremyDouglass

4

MongoDBのドキュメント「『』キー名のどこにも文字を含めないでください。」エンコーディングスキームを考え出すか、それなしでやらなければならないようです。


4

キーをエスケープする必要があります。ほとんどの人は文字列を適切にエスケープする方法を知らないようですので、手順は次のとおりです。

  1. エスケープ文字を選択します(めったに使用されない文字を選択するのが最適です)。例えば。'〜'
  2. エスケープするには、最初にエスケープ文字のすべてのインスタンスをエスケープ文字が前に付いたシーケンスに置き換え(例: '〜'-> '〜t')、次にエスケープする必要のある文字またはシーケンスをエスケープ文字が前に付いたシーケンスに置き換えます。例えば。'。' -> '〜p'
  3. エスケープを解除するには、最初に2番目のエスケープシーケンスのすべてのインスタンスからエスケープシーケンスを削除し(例: '〜p'-> '。')、次にエスケープ文字シーケンスを単一のエスケープ文字に変換します(例: '〜s'-> '〜 ')

また、mongoではキーを「$」で始めることも許可されていないため、そこで同様のことを行う必要があることを忘れないでください

これを行うコードは次のとおりです。

// returns an escaped mongo key
exports.escape = function(key) {
  return key.replace(/~/g, '~s')
            .replace(/\./g, '~p')
            .replace(/^\$/g, '~d')
}

// returns an unescaped mongo key
exports.unescape = function(escapedKey) {
  return escapedKey.replace(/^~d/g, '$')
                   .replace(/~p/g, '.')
                   .replace(/~s/g, '~')
}

'。〜p。'のような文字列を取得した場合、このエスケープはまだ壊れている可能性があります。ここで、エスケープされた文字列は '〜p ~~ p〜p'になります。エスケープを解除すると、実際の文字列とは異なる「。〜 ..」が表示されます。
jvc 2017

1
@jvcその通りです!説明とエスケープ関数の例を修正しました。それらがまだ壊れているかどうか私に知らせてください!
BT

3

遅い答えですが、SpringとMongoを使用している場合、Springはを使用して変換を管理できますMappingMongoConverter。これはJohnnyHKによるソリューションですが、Springによって処理されます。

@Autowired
private MappingMongoConverter converter;

@PostConstruct
public void configureMongo() {
 converter.setMapKeyDotReplacement("xxx");
}

保存されているJsonが次の場合:

{ "axxxb" : "value" }

Spring(MongoClient)を通じて、次のように読み取られます。

{ "a.b" : "value" }

タイプ 'org.springframework.data.mongodb.core.convert.MappingMongoConverter'のBeanが必要でしたが、見つかりませんでした。
Sathya NarayanC19年

1

オブジェクトキーごとに、JavaScriptで次のエスケープを使用します。

key.replace(/\\/g, '\\\\').replace(/^\$/, '\\$').replace(/\./g, '\\_')

私が気に入っているのは$、最初にのみ置換され、コンソールで使用するのが難しいユニコード文字を使用しないことです。_私にとっては、Unicode文字よりもはるかに読みやすいです。また、特殊文字($.)の1つのセットを別の(unicode)に置き換えることもありません。しかし、従来ので適切に脱出し\ます。


3
また、誰かがキーのいずれかに_を使用すると、バグが発生します。
BT

1

完璧ではありませんが、ほとんどの状況で機能します。禁止されている文字を別のものに置き換えます。キーに含まれているため、これらの新しい文字はかなりまれなはずです。

/** This will replace \ with ⍀, ^$ with '₴' and dots with ⋅  to make the object compatible for mongoDB insert. 
Caveats:
    1. If you have any of ⍀, ₴ or ⋅ in your original documents, they will be converted to \$.upon decoding. 
    2. Recursive structures are always an issue. A cheap way to prevent a stack overflow is by limiting the number of levels. The default max level is 10.
 */
encodeMongoObj = function(o, level = 10) {
    var build = {}, key, newKey, value
    //if (typeof level === "undefined") level = 20     // default level if not provided
    for (key in o) {
        value = o[key]
        if (typeof value === "object") value = (level > 0) ? encodeMongoObj(value, level - 1) : null     // If this is an object, recurse if we can

        newKey = key.replace(/\\/g, '⍀').replace(/^\$/, '₴').replace(/\./g, '⋅')    // replace special chars prohibited in mongo keys
        build[newKey] = value
    }
    return build
}

/** This will decode an object encoded with the above function. We assume the structure is not recursive since it should come from Mongodb */
decodeMongoObj = function(o) {
    var build = {}, key, newKey, value
    for (key in o) {
        value = o[key]
        if (typeof value === "object") value = decodeMongoObj(value)     // If this is an object, recurse
        newKey = key.replace(/⍀/g, '\\').replace(/^₴/, '$').replace(/⋅/g, '.')    // replace special chars prohibited in mongo keys
        build[newKey] = value
    }
    return build
}

これがテストです:

var nastyObj = {
    "sub.obj" : {"$dollar\\backslash": "$\\.end$"}
}
nastyObj["$you.must.be.kidding"] = nastyObj     // make it recursive

var encoded = encodeMongoObj(nastyObj, 1)
console.log(encoded)
console.log( decodeMongoObj( encoded) )

および結果-値は変更されないことに注意してください。

{
  sub⋅obj: {
    ₴dollar⍀backslash: "$\\.end$"
  },
  ₴you⋅must⋅be⋅kidding: {
    sub⋅obj: null,
    ₴you⋅must⋅be⋅kidding: null
  }
}
[12:02:47.691] {
  "sub.obj": {
    $dollar\\backslash: "$\\.end$"
  },
  "$you.must.be.kidding": {
    "sub.obj": {},
    "$you.must.be.kidding": {}
  }
}

1

デバッグ目的ではなくアプリケーション使用することをお勧めしないクエリを実行するための醜い方法がいくつかあります(埋め込みオブジェクトでのみ機能します)。

db.getCollection('mycollection').aggregate([
    {$match: {mymapfield: {$type: "object" }}}, //filter objects with right field type
    {$project: {mymapfield: { $objectToArray: "$mymapfield" }}}, //"unwind" map to array of {k: key, v: value} objects
    {$match: {mymapfield: {k: "my.key.with.dot", v: "myvalue"}}} //query
])

1

別のユーザーが述べたように、これのエンコード/デコードは将来問題になる可能性があるため、ドットのあるすべてのキーを置き換える方がおそらく簡単です。これは、キーを「。」に置き換えるために作成した再帰関数です。発生:

def mongo_jsonify(dictionary):
    new_dict = {}
    if type(dictionary) is dict:
        for k, v in dictionary.items():
            new_k = k.replace('.', '-')
            if type(v) is dict:
                new_dict[new_k] = mongo_jsonify(v)
            elif type(v) is list:
                new_dict[new_k] = [mongo_jsonify(i) for i in v]
            else:
                new_dict[new_k] = dictionary[k]
        return new_dict
    else:
        return dictionary

if __name__ == '__main__':
    with open('path_to_json', "r") as input_file:
        d = json.load(input_file)
    d = mongo_jsonify(d)
    pprint(d)

このコードを変更して、「$」を置き換えることもできます。これは、mongoがキーで許可しない別の文字であるためです。


0

PHPの場合、ピリオドの代わりにHTML値を使用します。それは"."です。

次のようにMongoDBに保存されます。

  "validations" : {
     "4e25adbb1b0a55400e030000" : {
     "associate" : "true" 
    },
     "4e25adb11b0a55400e010000" : {
       "associate" : "true" 
     } 
   } 

とPHPコード...

  $entry = array('associate' => $associate);         
  $data = array( '$set' => array( 'validations.' . str_replace(".", `"."`, $validation) => $entry ));     
  $newstatus = $collection->update($key, $data, $options);      

0

Lodashペアを使用すると変更できます

{ 'connect.sid': 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' }

[ [ 'connect.sid',
's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' ] ]

を使用して

var newObj = _.pairs(oldObj);

0

そのまま保存して、かなり後で変換できます

この例をLivescriptで作成しました。livescript.netWebサイトを使用して評価できます

test =
  field:
    field1: 1
    field2: 2
    field3: 5
    nested:
      more: 1
      moresdafasdf: 23423
  field3: 3



get-plain = (json, parent)->
  | typeof! json is \Object => json |> obj-to-pairs |> map -> get-plain it.1, [parent,it.0].filter(-> it?).join(\.)
  | _ => key: parent, value: json

test |> get-plain |> flatten |> map (-> [it.key, it.value]) |> pairs-to-obj

それは生成されます

{"field.field1":1,
 "field.field2":2,
 "field.field3":5,
 "field.nested.more":1,
 "field.nested.moresdafasdf":23423,
 "field3":3}


0

ヒントを教えてください:JSON.stringifyを使用してオブジェクトを保存できます/配列にはキー名にドットが含まれています次にJSON.parseを使用して文字列をオブジェクトに解析し、データベースからデータを取得するときに処理します

別の回避策:次のようにスキーマを再構築します。

key : {
"keyName": "a.b"
"value": [Array]
}

0

最新のMongoDBはドット付きのキーをサポートしていますが、javaMongoDB-driverはサポートしていません。そこで、Javaで動作させるために、java-mongo-driverのgithubリポジトリからコードを取得し、それに応じてisValid Key関数に変更を加え、それから新しいjarを作成し、現在それを使用しています。


0

dot(.)またはdollar($)を、実際のドキュメントでは使用されない他の文字に置き換えます。そして、dot(.)またはdollar($、ドキュメントを取得するときに)をます。この戦略は、ユーザーが読み取るデータには影響しません。

すべてのキャラクターからキャラクターを選択できます。


0

奇妙なことに、mongojsを使用すると、自分で_idを設定するとドット付きのドキュメントを作成できますが、_idの生成時にドキュメントを作成できません。

動作します:

db.testcollection.save({"_id": "testdocument", "dot.ted.": "value"}, (err, res) => {
    console.log(err, res);
});

動作しません:

db.testcollection.save({"dot.ted": "value"}, (err, res) => {
    console.log(err, res);
});

ドットキーでドキュメントを更新するデータも機能すると最初は思いましたが、ドットをサブキーとして識別します。

mongojsがドット(サブキー)をどのように処理するかを見て、キーにドットが含まれていないことを確認します。


0

どのような@JohnnyHKが言及している、削除句読点や操作を行います「」データがより大きなデータセットに蓄積され始めると、はるかに大きな問題が発生するため、キーから。これは特に、エラーをスローするキーへのアクセスと比較を必要とする$ mergeのような集計演算子を呼び出すときに問題を引き起こします。私はそれを難し​​い方法で学びました。始めている人のために繰り返さないでください。


-2

/home/user/anaconda3/lib/python3.6/site-packages/pymongo/collection.py

エラーメッセージで見つかりました。を使用する場合anaconda(そうでない場合は対応するファイルを検索)、上記のファイルの値をからcheck_keys = Trueに変更するだけFalseです。それはうまくいくでしょう!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.