104

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

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

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

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

4

24 回答 24

91

MongoDB 不支持其中带有点的键,因此您必须在导入 JSON 文件之前对其进行预处理以删除/替换它们,否则您将为各种问题做好准备。

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

于 2012-09-12T22:59:57.447 回答
26

正如其他答案中提到的,由于字段名称的限制, 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 转义语法中所述,但所描述的替换序列也同样有效。

于 2015-05-15T08:22:54.517 回答
19

Mongo 文档建议用它们的unicode 等价物替换非法字符,例如$和。.

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

于 2014-09-19T15:24:35.737 回答
19

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

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

于 2018-01-11T07:30:42.680 回答
13

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

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

您仍然可以很容易地查询这个,只需find对字段keyName keyValue执行操作。

所以而不是:

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

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

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

它将返回预期的文档。

于 2015-12-23T19:02:23.953 回答
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) ];
}
于 2014-04-28T03:05:10.360 回答
8

现在支持

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

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

于 2019-07-19T06:24:42.180 回答
4

来自MongoDB 文档“'.' 字符不得出现在键名中的任何位置”。看起来你必须想出一个编码方案或者不做。

于 2010-04-26T21:38:57.037 回答
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, '~')
}
于 2016-10-28T01:36:33.483 回答
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" }
于 2017-09-26T14:06:11.433 回答
3

正如另一位用户提到的,编码/解码将来可能会出现问题,因此替换所有带点的键可能更容易。这是我用“。”替换键的递归函数。发生:

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 不允许在键中使用的另一个字符。

于 2020-05-26T21:28:39.147 回答
1

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

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

我喜欢它的是它只$在开头替换,并且不使用在控制台中使用起来可能很棘手的 unicode 字符。_对我来说比 unicode 字符更具可读性。它也不会将一组特殊字符 ( $, .) 替换为另一组 (unicode)。但正确地使用传统\的 .

于 2016-09-03T17:39:00.060 回答
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": {}
  }
}
于 2018-10-04T16:09:18.847 回答
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
])
于 2020-02-27T11:04:18.657 回答
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);      
于 2011-07-19T21:14:02.787 回答
0

Lodash 对将允许您更改

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

进入

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

使用

var newObj = _.pairs(oldObj);
于 2015-10-02T09:48:20.927 回答
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}

于 2016-09-28T10:26:10.947 回答
0

给你我的提示:你可以使用 JSON.stringify 保存 Object/ Array 包含键名有点,然后使用 JSON.parse 将字符串解析为 Object 以在从数据库获取数据时进行处理

另一种解决方法:重组您的架构,如:

key : {
"keyName": "a.b"
"value": [Array]
}
于 2017-12-28T02:41:15.557 回答
0

最新的 MongoDB 确实支持带点的键,但 java MongoDB-driver 不支持。所以为了让它在 Java 中工作,我从java-mongo-driver 的 github repo 中提取代码,并在他们的 isValid Key 函数中进行了相应的更改,从中创建了新的 jar,现在使用它。

于 2018-03-22T16:24:46.690 回答
0

将点(.)或美元($)替换为在真实文档中永远不会使用的其他字符。并在检索文档时恢复点(.)或美元( )。$该策略不会影响用户读取的数据。

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

于 2018-06-12T04:01:54.133 回答
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);
});

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

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

于 2019-01-22T09:19:48.907 回答
0

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

于 2020-04-08T03:51:19.110 回答
0

在我们的例子中,带有句点的属性永远不会被用户直接查询。但是,它们可以由用户创建。

所以我们首先序列化我们的整个模型并用字符串替换特定字段的所有实例。我们的周期字段可以出现在许多位置,并且无法预测数据的结构是什么。

    var dataJson = serialize(dataObj);
    foreach(pf in periodFields) 
    {  
         var encodedPF = pf.replace(".", "ENCODE_DOT");
         dataJson.replace(pf, encodedPF);
    }

然后在我们的数据被展平后,我们替换编码PF的实例,以便我们可以在我们的文件中写入解码版本

没有人会需要一个名为 ENCODE_DOT 的字段,所以在我们的例子中这不会是一个问题。

结果是以下 color.one 将作为 colorENCODE_DOTone 在数据库中

当我们编写文件时,我们将 ENCODE_DOT 替换为 .

于 2022-01-07T23:38:41.340 回答
-2

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

在错误消息中找到它。如果您使用anaconda(如果没有,请查找对应的文件),只需将上述文件中的值从 更改check_keys = TrueFalse。那行得通!

于 2017-10-16T10:17:05.087 回答