密钥名称中的MongoDB点(。)

时间:2012-09-12 22:42:29

标签: javascript mongodb nosql

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

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

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

我这样做是错误的,不应该使用像外部数据那样的哈希映射(即模型),还是可以以某种方式逃避点?也许我在想类似Javascript。

23 个答案:

答案 0 :(得分:56)

MongoDB不支持keys with a dot,因此您必须预先处理JSON文件以在导入之前删除/替换它们,否则您将为各种问题做好准备。< / p>

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

答案 1 :(得分:18)

Mongo docs建议将$.等非法字符替换为其unicode等效字符。

  

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

答案 2 :(得分:15)

正如其他答案所述,由于restrictions on field names,MongoDB不允许$.个字符作为地图密钥。但是,如Dollar Sign Operator Escaping 中所述,此限制不会阻止您使用此类密钥插入文档,它只会阻止您更新或查询它们。

简单地将.替换为[dot](如评论中所述)的问题是,当用户合法地想要存储密钥[dot]时会发生什么?

Fantom's afMorphia driver采用的方法是使用类似于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 escape syntax in Java中提到的完整/正确的unicode转义规则,但所描述的替换序列也同样有效。

答案 3 :(得分: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) ];
}

答案 4 :(得分:9)

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

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

您仍然可以通过在字段 keyName keyValue 上执行find来轻松查询此内容。

所以而不是:

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

实际上不会按预期工作,你会运行:

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

它将返回预期的文件。

答案 5 :(得分:9)

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

Field names can contain dots (.) and dollar ($) characters now

答案 6 :(得分:4)

来自MongoDB docs“the'。'字符不得出现在键名称中的任何位置“。看起来你必须想出一个编码方案,或者没有。

答案 7 :(得分:3)

你需要逃脱钥匙。由于似乎大多数人不知道如何正确地逃避字符串,这里是步骤:

  1. 选择一个转义字符(最好选择一个很少使用的字符)。例如。 '〜'
  2. 要逃脱,首先用转义字符前面的一些序列替换转义字符的所有实例(例如'〜' - &gt;'〜t'),然后用一些前序列替换你需要转义的任何字符或序列与你的逃脱角色。例如。 '' - &GT; '〜P'
  3. 要进行unescape,首先从第二个转义序列的所有实例中删除转义序列(例如'~p' - &gt;'。'),然后将转义字符序列转换为单个转义字符(例如'~s') - &gt;'〜')
  4. 另外,请记住mongo也不允许键以'$'开头,所以你必须做类似的事情

    以下是一些代码:

    class MyTextStripper : PDFTextStripper
    {
    
        protected override void processTextPosition(TextPosition text)
        {
            base.processTextPosition(text);
            Console.WriteLine("X: " + text.getX() +
                " y: " + text.getY() +
                " height: " + text.getHeight() +
                " width: " + text.getWidth() +
                " word: " + text.getCharacter());
    
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
    
            ExtractTextFromPdf(@"C:\Users\Desktop\mathml88.pdf");
        }
    
        private static string ExtractTextFromPdf(string path)
        {
            PDDocument doc = null;
            try
            {
                doc = PDDocument.load(path);
    
                MyTextStripper stripper = new MyTextStripper();
    
                return stripper.getText(doc);
            }
            finally
            {
                if (doc != null)
                {
                    doc.close();
                }
            }
        }
    }
    

答案 8 :(得分:3)

迟到的答案,但如果您使用Spring和Mongo,Spring可以使用Values = new ChartValues<double> { test } 为您管理转换。这是JohnnyHK的解决方案,但由Spring处理。

MappingMongoConverter

如果您存储的Json是:

@Autowired
private MappingMongoConverter converter;

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

通过Spring(MongoClient),它将被读作:

{ "axxxb" : "value" }

答案 9 :(得分: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不允许在键中使用的另一个字符。

答案 10 :(得分: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
])

答案 11 :(得分:1)

现在受支持

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

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

答案 12 :(得分:1)

并不完美,但是在大多数情况下都可以使用:用其他东西替换禁止使用的字符。由于它是密钥,因此这些新字符应该相当少。

import android.support.design.widget.BottomNavigationView;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.TabHost;

import adapters.MyHorizontalAdapter;
import adapters.MyVerticalAdapter;
import models.ModelVertical;
import models.ModelsHorizontal;



public class MainActivity extends AppCompatActivity {
RecyclerView verticalRecyclerView , horizontalRecyclerView;
TabHost host;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
verticalRecyclerView = findViewById(R.id.verticalRecycler);
horizontalRecyclerView = findViewById(R.id.storiesRecycler);
BottomNavigationView bottomNavigationView= findViewById(R.id.bottomNv);
bottomNavigationView.setItemIconSize(100);




host = findViewById(R.id.tabHost);
host.setup();

TabHost.TabSpec spec = host.newTabSpec("Messages");

spec.setContent(R.id.tab1);
spec.setIndicator("Messages");
host.addTab(spec);

host.setHorizontalScrollBarEnabled(true);
host.setup();


spec = host.newTabSpec("Active");
spec.setContent(R.id.tab2);
spec.setIndicator("Active");
host.addTab(spec);

spec = host.newTabSpec("Groups");
spec.setContent(R.id.tab3);
spec.setIndicator("Groups");
host.addTab(spec);


spec = host.newTabSpec("Calls");
spec.setContent(R.id.tab4);
spec.setIndicator("Calls");
host.addTab(spec);

// ***** Assigning Vertical Recycler Data
ModelVertical[] modelVerticals = {new ModelVertical(R.drawable.ahmed , 
"Ahmed" , "Mahmoud It's Me Ahmed " , "Thursday" , R.drawable.ahmed),
new ModelVertical(R.drawable.aly , "Aly" , "Hello Mahmous It's Me Aly" , 
"Tuesday" , R.drawable.aly),
new ModelVertical(R.drawable.elaayek , "Elaayeek" , "Hello Mahmoud It's Me 
Elaayek" , "Monday" , R.drawable.elaayek),
new ModelVertical(R.drawable.fathy , " Fathy" , "Hello Mahmoud It's Me 
Fathy",  "Sunday" , R.drawable.fathy),
new ModelVertical(R.drawable.samy , " Samy" , "Hello Mahmoud It's Me 
Samy",  "Saturday" , R.drawable.samy),
new ModelVertical(R.drawable.wael , " Wael" , "Hello Mahmoud It's Me 
Wael",  "Wednesday" , R.drawable.wael)
};

ModelsHorizontal[] modelsHorizontals = {new 
ModelsHorizontal(R.drawable.ic_camera2 , " Add to your story"),
new ModelsHorizontal(R.drawable.ahmed , "Ahmed"),
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.elaayek , " Elaayek "),
new ModelsHorizontal(R.drawable.ibraheem , " Ibrahim" ) ,
new ModelsHorizontal(R.drawable.omar , "Omar")
};
verticalRecyclerView.setLayoutManager(new LinearLayoutManager(this)        
MyVerticalAdapter myAdapter = new MyVerticalAdapter(modelVerticals);
verticalRecyclerView.setAdapter(myAdapter);

horizontalRecyclerView.setLayoutManager(new LinearLayoutManager(this , 
LinearLayoutManager.HORIZONTAL , false));
MyHorizontalAdapter myHorizontalAdapter = new 
MyHorizontalAdapter(modelsHorizontals);
horizontalRecyclerView.setAdapter(myHorizontalAdapter);
}
}

这是一个测试:

/** 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) )

答案 13 :(得分:1)

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

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

我喜欢的是它在开头只替换$,并且它不使用在控制台中使用的unicode字符。 _对我来说比unicode角色更具可读性。它也不会将一组特殊字符($.)替换为另一组(unicode)。但适当地逃避了传统的\

答案 14 :(得分:0)

给你我的提示:你可以使用JSON.stringify来保存对象/数组包含键名称有点,然后解析字符串到对象用JSON.parse处理从数据库获取数据时

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

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

答案 15 :(得分:0)

最新的MongoDB确实支持带点的键,但java MongoDB驱动程序不支持。因此,为了使它在Java中工作,我从github repo of java-mongo-driver中提取代码并在其isValid Key函数中相应地进行了更改,从中创建了新jar,现在使用它。

答案 16 :(得分:0)

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

您可以从all characters中选择字符。

答案 17 :(得分: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}

答案 18 :(得分: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如何处理点(子键),我将确保我的键不包含点。

答案 19 :(得分:0)

Lodash pairs将允许您更改

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

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

使用

var newObj = _.pairs(oldObj);

答案 20 :(得分:0)

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

答案 21 :(得分:0)

对于PHP,我将HTML值替换为句点。那是"&#46;"

它存储在MongoDB中:

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

和PHP代码......

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

答案 22 :(得分:-1)

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

在错误消息中找到它。如果您使用anaconda(如果没有,请查找通讯文件),只需将上述文件中的值从check_keys = True更改为False即可。那会有用的!