键名中的 MongoDB 点 (.)

IT技术 javascript mongodb nosql
2021-01-20 12:45:40

似乎 mongo 不允许插入带有点 (.) 或美元符号 ($) 的键,但是当我使用 mongoimport 工具导入一个包含点的 JSON 文件时,它工作正常。驱动程序抱怨试图插入该元素。

这是文档在数据库中的样子:

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

我做这一切都错了吗,不应该像这样使用带有外部数据(即模型)的哈希映射,或者我可以以某种方式逃避点吗?也许我在想太多类似 Javascript 的东西。

6个回答

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

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

@TzuryBarYochay OMG 你已经找到了相当于西北通道的 MongoDB。我认为这应该是公认的答案。
2021-03-21 12:45:40
@emarel db.collection_foo.update({this: "that"}, {$set: {a:"b"}}, {check_keys: false})
2021-03-27 12:45:40
再次遇到这种情况。这似乎不是发生在我们可以控制并经常需要查询的应用程序键名上,而是在嵌套数据结构中用户提供的数据,我们无法控制,但是 (a) 想存储在 Mongo 中,(b)我们知道这可能发生在哪些特定字段(例如models这里),以及(c)我们不需要在 Mongo 中通过键名查询它们。因此,我确定的一个模式是JSON.stringify保存时使用此字段,检索时使用 'JSON.parse`。
2021-03-29 12:45:40
我认为没有标准的方法,最好的方法太依赖于具体情况。但是如果可能的话,我会避免使用任何关键的编码器/解码器方法,因为您将继续永久地支付这种不便,其中 JSON 重组可能是一次性成本。
2021-04-01 12:45:40
如果必须,您可以提供 {check_keys: false} 选项来绕过此问题。
2021-04-10 12:45:40

正如其他答案中提到的,由于对字段名称的限制, MongoDB 不允许$.字符作为映射键但是,正如美元符号运算符逃避此限制中所述,不会阻止您插入带有此类密钥的文档,它只会阻止您更新或查询它们。

简单地替换.[dot]or U+FF0E(如本页其他地方所述)的问题是,当用户合法地想要存储密钥[dot]or时会发生什么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 实际上执行完整/正确的 Unicode 转义规则,如Java中的Unicode 转义语法中所述但所描述的替换序列也同样有效。

嗨@Moonlit,好问题。这是因为示例是用Fantom编写的,并且 $ 字符是为字符串插值保留的,因此需要使用反斜杠进行转义。所以实际上,是的,它只是替换“$”。
2021-03-14 12:45:40
事实证明,Mongodb 在其最新版本中支持点和美元。请参阅:- stackoverflow.com/a/57106679/3515086
2021-03-31 12:45:40
应该用于//g替换所有出现的事件,而不仅仅是第一个。此外,使用 Martin Konecny 的答案中的全角等效项似乎是一个好主意。最后,一个反斜杠足以进行编码。key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
2021-04-01 12:45:40
@cw' - 代码采用类似 Java 的语法,因此替换实际上会替换所有出现的内容,并且需要双反斜杠来转义反斜杠。同样,您需要引入某种形式的转义以确保涵盖所有情况。有些人,在某个时候,可能真的想要一个U+FF04.
2021-04-04 12:45:40
为什么是 \$ 而不仅仅是 $ 呢?
2021-04-05 12:45:40

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

在这些情况下,键需要替换保留的 $ 和 。人物。任何字符都可以,但请考虑使用 Unicode 全角等效项:U+FF04(即“$”)和 U+FF0E(即“.”)。

这听起来像是解决大量调试难题的秘诀。
2021-03-12 12:45:40
-1 A. 这是一个糟糕的主意——如果有人真的试图使用这些 un​​icode 字符作为密钥怎么办?然后你有一个静默错误,谁知道你的系统会发生什么。不要使用这样模棱两可的转义方法。B. mongo 文档不再这么说,可能是因为有人意识到这是一个糟糕的主意
2021-03-13 12:45:40
@SergioTulentsev 我让他们删除了建议:) github.com/mongodb/docs/commit/...
2021-03-16 12:45:40
@AndrewMedico,@tamlyn - 我认为文档的意思是 db.test.insert({"field\uff0ename": "test"})
2021-04-01 12:45:40
@BT:给你的帽子提示,先生:)
2021-04-06 12:45:40

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

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

确实,我只是尝试了设置mongodb-4.1.1pymongo-3.7.1. 我可以.使用 robomongo添加包含密钥的文档,但不能添加 from pymongo,它会引发InvalidDocument: key '1.1' must not contain '.'希望它现在已经修复......
2021-03-12 12:45:40
我尝试使用 mongodb 服务器 4.0.9 和 java 驱动程序 3.10.2,但它不接受键名中的点。奇怪的是,当尝试使用 robomongo 时它起作用了......
2021-03-17 12:45:40
使用 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 失败。
2021-03-19 12:45:40
即使服务器现在支持它,驱动程序仍然会检查密钥中的 $ 和点并且不接受它们。因此 Mongo 理论上只支持点和美元字符。实际上这还不能用:(
2021-03-27 12:45:40
也许您正在使用一些旧的或不兼容的客户端。我一直在我的生产服务器上毫不费力地使用它。我已经检查过 NodeJS 和 Java 客户端。
2021-03-30 12:45:40

我刚刚实施的一个我非常满意的解决方案涉及将键名和值拆分为两个单独的字段。这样,我可以保持字符完全相同,而不必担心任何解析噩梦。文档看起来像:

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

您仍然可以很容易地进行查询,只需findkeyName keyValue字段上执行 a 即可

所以而不是:

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

这实际上不会按预期工作,您将运行:

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

它将返回预期的文档。

我添加了一个查询示例。这有帮助吗?
2021-03-19 12:45:40
你是怎么做到的?你能帮我处理同样的情况吗?
2021-04-11 12:45:40