密钥名称中的MongoDB点(。)


93

似乎mongo不允许插入带有点(。)或美元符号($)的键,但是当我使用mongoimport工具导入其中包含点的JSON文件时,效果很好。驱动程序抱怨试图插入该元素。

这是文档在数据库中的外观:

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

我是不是做错了,不应该使用带有外部数据(例如模型)的哈希图,还是可以以某种方式逃脱点?也许我在想太多类似Javascript的东西。


Answers:


85

MongoDB不支持键中带有圆点的键,因此在导入之前,您必须预处理JSON文件以删除/替换它们,否则您将面临各种问题。

没有标准的解决方法可以解决此问题,最好的方法过于依赖于具体情况。但是,如果可能的话,我会避免使用任何关键的编码器/解码器方法,因为您将继续永久性地给它带来不便,因为JSON重组可能是一次性的成本。


1
我认为没有标准的方法,最好的方法过于依赖于具体情况。但是,如果可能的话,我会避免使用任何关键的编码器/解码器方法,因为您将继续永久性地给它带来不便,因为JSON重组可能是一次性的成本。
JohnnyHK

8
再次遇到这种情况。这种情况似乎很少发生于我们可以控制并且经常需要查询的应用程序键名,而是用户无法控制的嵌套数据结构中的用户提供的数据,但是(a)希望存储在Mongo中,(b)我们知道这可能发生在哪些特定字段(例如models此处),以及(c)我们不知道;不需要在Mongo中按键名查询它们。因此,我确定的模式是JSON.stringify保存时到此字段,而检索时到'JSON.parse`。
原型

16
如果需要,您可以提供{check_keys:false}选项来绕过此问题。
Tzury Bar Yochay

5
@TzuryBarYochay OMG,您已经找到了与西北通道等效的MongoDB。我认为这应该是公认的答案。
原型

2
@emarel db.collection_foo.update({this:“ that”},{$ set:{a:“ b”}},{check_keys:false})
Tzury Bar Yochay

22

如其他答案所述,由于以下原因,MongoDB不允许将$.字符作为映射键字段名的限制,。但是,如“美元符号运算符”中 所述,转义该限制并不能阻止您使用此类密钥插入文档,而只是阻止您对其进行更新或查询。

简单更换的问题 .[dot]U+FF0E(如别处此页面上提到的),当用户合法想要存储密钥会发生什么[dot]U+FF0E

一种方法 Fantom的 afMorphia驱动程序采用的是使用类似于Java的unicode转义序列,但要确保先转义转义字符。本质上,将进行以下字符串替换(*):

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

随后MongoDB中读取映射密钥时,将进行反向替换。

或在 Fantom代码中:

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实际上执行JavaUnicode转义语法中提到的完整/适当的Unicode转义规则,但所描述的替换序列也能正常工作。


应该用于//g替换所有事件,而不仅仅是第一次。同样,使用马丁·科涅克尼(Martin Konecny)的答案中的全角等效项似乎是个好主意。最后,一个反斜杠就足以进行编码。key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
cw'16年

1
@cw'-代码使用类似Java的语法,因此replace实际上会替换所有出现的内容,并且转义反斜杠需要双反斜杠。同样,您需要引入某种形式的转义以确保涵盖所有情况。某人有时可能实际上想要的密钥U+FF04
史蒂夫·艾农

2
事实证明,Mongodb的最新版本支持点和美元。请参阅: - stackoverflow.com/a/57106679/3515086
Abhidemon

18

蒙戈文档建议替换非法字符,如$.他们的Unicode的等价物。

在这种情况下,密钥将需要替换保留的$和。字符。任何字符都足够,但是请考虑使用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:先生,给您戴上帽子的提示,):
塞尔吉奥·图伦采夫

15

MongoDB的最新稳定版本(v3.6.1)确实在键或字段名称中支持点(。)。

字段名称现在可以包含点(。)和美元($)字符


10
即使服务器现在支持它,驱动程序仍然会检查键中的$和点,并且不接受它们。因此,Mongo理论上仅支持点和美元字符。实际上,这尚不可用:(
JMax,

也许您使用的是旧的或不兼容的客户端。我一直在我的生产服务器上使用此工具,而不会费力。我已经检查了NodeJS和Java客户端。
h4ck3d

使用Java绝对不能正常工作!请尝试以下命令:mongoClient.getDatabase("mydb").getCollection("test").insertOne(new Document("value", new Document("key.with.dots", "value").append("$dollar", "value")));使用mongodb-driver.3.6.3和MongoDB 3.6.3失败。
JMax,

1
实际上,我只是尝试了一个设置mongodb-4.1.1pymongo-3.7.1。我可以.用robomongo添加包含密钥的文档,但不能从中添加pymongo,这仍然InvalidDocument: key '1.1' must not contain '.'希望现在已经解决了……
学习

我尝试使用mongodb服务器4.0.9和Java驱动程序3.10.2,但是它在键名中不接受点。奇怪的是,当尝试使用robomongo时,它起作用了……
xyzt

12

我刚刚实现的令我非常满意的解决方案包括将键名和值分成两个单独的字段。这样,我可以使字符完全相同,而不用担心解析这些噩梦。该文档将如下所示:

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

您仍然可以轻松地对此进行查询,只需findkeyName 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
我喜欢这种带有1向哈希的干净解决方案,并且确实类似于幕后工作方式。
Michael Yagudaev 2014年

3
使用散列作为键的问题在于,不能保证它们是唯一的,并且经常会产生碰撞。加上每次您想访问地图时都计算加密哈希对我来说似乎并不是最理想的解决方案。
史蒂夫·艾农

2
为什么这比用特殊字符或序列替换句点更好?
B

将字符串转换为base64更好。

8

现在支持

MongoDb 3.6及更高版本在字段名称中同时支持美元。参见下面的JIRA:https : //jira.mongodb.org/browse/JAVA-2810

将您的Mongodb升级到3.6+听起来是最好的方法。


这是最好的答案。:+1
hello_abhishek '19

3
3.6可以存储他们,是的,但它被支持,可能会引发驱动程序错误,并可能会破坏查询/更新:限制:“MongoDB的查询语言不能总是意味深长地表达了文件,其字段名称包含这些字符查询(见服务器- 30575)。在以查询语言添加支持之前,不建议在字段名称中使用$和。,并且MongoDB官方驱动程序不支持
JeremyDouglass

4

来自MongoDB文档“ the”。字符不能出现在键名中的任何位置”。看来您必须提出一种编码方案,否则就没有。


4

您需要转义键。由于大多数人似乎都不知道如何正确地转义字符串,因此,请执行以下步骤:

  1. 选择一个转义字符(最好选择一个很少使用的字符)。例如。'〜'
  2. 要进行转义,请先用转义字符前面的某个顺序替换转义字符的所有实例(例如'〜'->'〜t'),然后用您的转义字符前面的某个顺序替换需要转义的任何字符或顺序。例如。'。' ->'〜p'
  3. 要取消转义,请先从第二个转义序列的所有实例中删除转义序列(例如'〜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

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 Narayan C

1

对于每个对象键,我在JavaScript中使用以下转义:

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

我喜欢它的地方是它仅$在开始时才被替换,并且不使用unicode字符,而在控制台中很难使用。_对我而言,它比Unicode字符更具可读性。它还不会将一组特殊字符($.)替换为另一组(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.net网站进行评估

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确实支持带点的键,但Java MongoDB驱动程序不支持。因此,要使其在Java中工作,我从java-mongo-driver的github存储库中提取了代码,并对其isValid Key函数进行了相应的更改,并使用它创建了一个新的jar。


0

用其他在实际文档中永远不会使用的字符替换dot(.)或dollar($)。并在检索文档时还原dot(.)或dollar($)。该策略不会影响用户读取的数据。

您可以从所有字符中选择字符


0

奇怪的是,如果我自己设置_id,可以使用mongojs创建带有点的文档,但是在生成_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);
});

我首先想到dat用点键更新文档也可以,但是它可以将点标识为子键!

看看mongojs如何处理点(子键),我将确保我的键不包含点。


0

就像@JohnnyHK提到的一样,请删除标点符号或“。”。因为您的数据开始累积到更大的数据集中时,它将产生更大的问题。这将引起问题,尤其是当您调用诸如$ merge之类的聚合运算符时,该运算符需要访问和比较会引发错误的键。我已经学到了很难的方法,对于那些刚入门的人,请不要重复。


-2

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

在错误消息中找到它。如果你使用anaconda(找到对应的文件,如果没有),只需将值从check_keys = TrueFalse上述的文件中。可以的!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.