MongoDB titik (.) Di nama kunci


95

Tampaknya mongo tidak mengizinkan penyisipan kunci dengan titik (.) Atau tanda dolar ($) namun ketika saya mengimpor file JSON yang berisi titik di dalamnya menggunakan alat mongoimport, itu berfungsi dengan baik. Pengemudi mengeluh tentang mencoba memasukkan elemen itu.

Seperti inilah tampilan dokumen di database:

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

Apakah saya melakukan ini semua salah dan seharusnya tidak menggunakan peta hash seperti itu dengan data eksternal (yaitu model) atau dapatkah saya melarikan diri dari titik entah bagaimana? Mungkin saya terlalu banyak berpikir seperti Javascript.


Jawaban:


87

MongoDB tidak mendukung kunci dengan titik di dalamnya sehingga Anda harus memproses file JSON Anda untuk menghapus / menggantinya sebelum mengimpornya atau Anda akan menyiapkan diri untuk segala macam masalah.

Tidak ada solusi standar untuk masalah ini, pendekatan terbaik terlalu bergantung pada spesifik situasi. Tapi saya akan menghindari pendekatan encoder / decoder kunci jika memungkinkan karena Anda akan terus membayar ketidaknyamanan itu untuk selamanya, di mana restrukturisasi JSON mungkin akan menjadi biaya satu kali.


1
Saya tidak berpikir ada cara standar, pendekatan terbaik terlalu bergantung pada spesifik situasi. Tapi saya akan menghindari pendekatan encoder / decoder kunci jika memungkinkan karena Anda akan terus membayar ketidaknyamanan itu untuk selamanya, di mana restrukturisasi JSON mungkin akan menjadi biaya satu kali.
JohnnyHK

8
Mengalami situasi ini lagi. Hal ini tampaknya terjadi tidak begitu banyak dengan nama kunci aplikasi, yang dapat kita kontrol dan sering kali perlu kueri, tetapi dengan data yang disediakan pengguna dalam struktur data bersarang, yang tidak dapat kita kendalikan, tetapi (a) ingin disimpan dalam bahasa Mongo , (b) kita tahu di bidang spesifik mana hal ini mungkin terjadi (misalnya di modelssini), dan (c) kita tidak perlu menanyakannya dengan nama kunci di Mongo. Jadi pola yang saya JSON.stringifytetapkan adalah bidang ini di simpan, dan 'JSON.parse` saat diambil.
prototipe

16
Jika perlu, Anda dapat memberikan opsi {check_keys: false} untuk melewati masalah ini.
Tzury Bar Yochay

5
@TzuryBarYochay OMG Anda telah menemukan MongoDB yang setara dengan bagian barat laut. Saya pikir ini harus menjadi jawaban yang diterima.
prototipe

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

22

Seperti yang disebutkan dalam jawaban lain, MongoDB tidak mengizinkan $atau .karakter sebagai kunci peta karena batasan pada nama bidang . Namun, seperti yang disebutkan dalam Dollar Sign Operator Escaping pembatasan ini tidak mencegah Anda memasukkan dokumen dengan kunci seperti itu, ini hanya mencegah Anda untuk memperbarui atau menanyakannya.

Masalah hanya .dengan mengganti dengan [dot]atau U+FF0E(seperti yang disebutkan di tempat lain di halaman ini) adalah, apa yang terjadi ketika pengguna secara sah ingin menyimpan kunci [dot]atau U+FF0E?

Pendekatan yang diambil oleh pengemudi afMorphia dari Fantom , adalah dengan menggunakan urutan pelolosan unicode yang mirip dengan yang ada di Jawa, tetapi memastikan karakter pelolosan tersebut lolos terlebih dahulu. Intinya, penggantian string berikut dilakukan (*):

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

Penggantian terbalik dilakukan ketika kunci peta kemudian dibaca dari MongoDB.

Atau dalam kode Fantom :

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

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

Satu-satunya saat pengguna perlu menyadari konversi tersebut adalah saat membuat kueri untuk kunci tersebut.

Mengingat umum untuk menyimpan dotted.property.namesdalam database untuk tujuan konfigurasi, saya yakin pendekatan ini lebih disukai daripada hanya melarang semua kunci peta tersebut.

(*) afMorphia sebenarnya menjalankan aturan pelolosan unicode penuh / tepat seperti yang disebutkan dalam sintaks pelolosan Unicode di Java, tetapi urutan penggantian yang dijelaskan juga berfungsi dengan baik.


Harus digunakan //guntuk mengganti semua kejadian dan bukan hanya yang pertama. Juga, menggunakan ekuivalen lebar penuh seperti pada jawaban Martin Konecny ​​tampaknya merupakan ide yang bagus. Akhirnya, satu garis miring terbalik cukup untuk pengkodean. key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
cw '

1
@cw '- Kode berada dalam sintaks seperti Java, jadi ganti sebenarnya menggantikan semua kejadian, dan garis miring terbalik ganda diperlukan untuk mengosongkan garis miring terbalik. Dan sekali lagi, Anda perlu memperkenalkan beberapa bentuk pelarian untuk memastikan semua kasus tercakup. Seseorang, pada suatu waktu, mungkin benar-benar menginginkan kunci U+FF04.
Steve Eynon

2
Ternyata, Mongodb Mendukung titik dan dolar dalam versi terbarunya. Lihat: - stackoverflow.com/a/57106679/3515086
Abhidemon

18

Dokumen Mongo menyarankan untuk mengganti karakter ilegal seperti $dan .dengan padanan unicode mereka.

Dalam situasi ini, kunci perlu menggantikan $ dan. karakter. Setiap karakter sudah cukup, tetapi pertimbangkan untuk menggunakan persamaan lebar penuh Unicode: U + FF04 (yaitu "$") dan U + FF0E (yaitu ".").


74
Kedengarannya seperti resep untuk mengatasi sakit kepala debugging yang parah di masa mendatang.
tidak ada

2
@AndrewMedico, @tamlyn - Saya pikir dokumen itu berarti sepertidb.test.insert({"field\uff0ename": "test"})
P. Myer Nore

4
-1 A. Itu ide yang buruk - bagaimana jika seseorang benar-benar mencoba menggunakan karakter unicode tersebut sebagai kunci? Kemudian Anda memiliki kesalahan diam yang akan melakukan siapa yang tahu apa yang terjadi pada sistem Anda. Jangan gunakan metode melarikan diri yang ambigu seperti itu. B. the mongo docs tidak lagi mengatakan itu, mungkin karena seseorang menyadari itu ide yang buruk
BT

7
@SergioTulentsev Saya meminta mereka untuk menghapus rekomendasi :) github.com/mongodb/docs/commit/…
BT

2
@BT: tip topi untuk Anda, Pak :)
Sergio Tulentsev

15

Versi stabil terbaru (v3.6.1) dari MongoDB mendukung titik (.) Di kunci atau nama kolom sekarang.

Nama bidang sekarang dapat berisi titik (.) Dan karakter dolar ($)


10
Bahkan jika server mendukungnya sekarang, driver masih memeriksa $ dan titik di kunci dan tidak menerimanya. Oleh karena itu, Mongo secara teoritis hanya mendukung titik dan karakter dolar. Praktis ini belum bisa digunakan :(
JMax

Mungkin Anda menggunakan klien lama atau tidak kompatibel. Saya telah menggunakan ini di server produksi saya tanpa keringat. Saya telah memeriksa klien NodeJS dan Java.
h4ck3d

Dengan Java, itu pasti tidak berfungsi! Coba perintah berikut: mongoClient.getDatabase("mydb").getCollection("test").insertOne(new Document("value", new Document("key.with.dots", "value").append("$dollar", "value")));Gagal menggunakan mongodb-driver.3.6.3 dan MongoDB 3.6.3.
JMax

1
Memang, saya baru mencoba dengan pengaturan mongodb-4.1.1dan pymongo-3.7.1. Saya dapat menambahkan dokumen yang berisi kunci dengan .dengan robomongo tetapi tidak dari pymongo, itu sill menimbulkan InvalidDocument: key '1.1' must not contain '.'Berharap itu telah diperbaiki sekarang ...
Belajar itu berantakan

Saya mencoba dengan server mongodb 4.0.9 dan driver java 3.10.2 tetapi tidak menerima titik dalam nama kunci. aneh bahwa ketika mencoba menggunakan robomongo itu berhasil ...
xyzt

12

Solusi yang baru saja saya terapkan yang membuat saya sangat senang melibatkan pemisahan nama dan nilai kunci menjadi dua bidang terpisah. Dengan cara ini, saya dapat menyimpan karakter yang persis sama, dan tidak khawatir tentang mimpi buruk penguraian itu. Dokumen tersebut akan terlihat seperti:

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

Anda masih bisa membuat kueri ini cukup mudah, hanya dengan melakukan finddi bidang keyName dan keyValue .

Jadi, alih-alih:

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

yang sebenarnya tidak berfungsi seperti yang diharapkan, Anda akan menjalankan:

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

dan itu akan mengembalikan dokumen yang diharapkan.


Bagaimana Anda melakukannya? Bisakah Anda membantu saya dengan kasus yang sama.
profiler

Saya menambahkan contoh kueri. Apakah itu membantu?
Steve

10

Anda dapat mencoba menggunakan hash di kunci alih-alih nilainya, lalu simpan nilai itu dalam nilai 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
    ]
}

Anda kemudian akan mengakses model menggunakan hash nanti.

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

1
Saya suka ini, solusi bersih dengan hashing 1 arah dan sangat mirip dengan cara kerja di balik terpal.
Michael Yagudaev

3
Masalah dengan menggunakan hash sebagai kunci, adalah bahwa hash tidak dijamin unik, dan sering kali menghasilkan benturan . Ditambah menghitung hash kriptografi setiap kali Anda ingin mengakses peta sepertinya bukan solusi paling optimal bagi saya.
Steve Eynon

2
Mengapa ini lebih baik daripada mengganti titik dengan karakter atau urutan khusus?
B Tujuh

Mengonversi string ke base64 jauh lebih baik.
Zen

8

Ini didukung sekarang

MongoDb 3.6 dan seterusnya mendukung titik dan dolar dalam nama bidang. Lihat JIRA di bawah: https://jira.mongodb.org/browse/JAVA-2810

Mengupgrade Mongodb Anda menjadi 3.6+ terdengar seperti cara terbaik untuk melakukannya.


Ini adalah jawaban terbaik disini. : +1
hello_abhishek

3
3.6 dapat menyimpan mereka, ya, tapi itu tidak belum didukung, mungkin melemparkan kesalahan driver, dan bisa pecah permintaan / update: pembatasan : "The MongoDB Query Language dapat tidak selalu bermakna mengungkapkan permintaan atas dokumen yang namanya lapangan berisi karakter tersebut (lihat SERVER- 30575). Hingga dukungan ditambahkan dalam bahasa kueri, penggunaan $ dan. Dalam nama bidang tidak disarankan dan tidak didukung oleh driver resmi MongoDB. "
JeremyDouglass

4

Dari dokumen MongoDB "the '.' karakter tidak boleh muncul di mana pun dalam nama kunci ". Sepertinya Anda harus membuat skema encoding atau tanpa skema.


4

Anda harus melepaskan kunci. Karena tampaknya kebanyakan orang tidak tahu cara melepaskan string dengan benar, inilah langkah-langkahnya:

  1. pilih karakter pelarian (terbaik untuk memilih karakter yang jarang digunakan). Misalnya. '~'
  2. Untuk meloloskan diri, pertama-tama ganti semua contoh karakter pelolos dengan beberapa urutan yang diawali dengan karakter pelolosan Anda (misalnya '~' -> '~ t'), lalu ganti karakter atau urutan apa pun yang Anda perlukan untuk keluar dengan beberapa urutan yang diawali dengan karakter pelolosan Anda . Misalnya. '.' -> '~ p'
  3. Untuk membatalkan pelolosan, pertama hapus urutan pelolosan dari semua contoh urutan pelolosan kedua Anda (mis. '~ P' -> '.'), Kemudian ubah urutan karakter pelolosan Anda menjadi satu karakter pelolosan (mis. '~ S' -> '~ ')

Juga, ingat bahwa mongo juga tidak mengizinkan kunci untuk dimulai dengan '$', jadi Anda harus melakukan sesuatu yang serupa di sana

Berikut beberapa kode yang melakukannya:

// 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, '~')
}

Meloloskan diri ini masih bisa rusak, jika Anda mendapatkan string seperti '. ~ P.'. Di sini string yang lolos akan menjadi '~ p ~~ p ~ p'. Unescaping akan memberi Anda '. ~ ..', yang berbeda dari string sebenarnya.
jvc

1
@jvc Anda benar! Saya telah memperbaiki penjelasan dan contoh fungsi escape. Beri tahu saya jika masih rusak!
BT

3

Jawaban yang terlambat, tetapi jika Anda menggunakan Spring dan Mongo, Spring dapat mengatur konversi untuk Anda dengan MappingMongoConverter. Ini solusinya oleh JohnnyHK tetapi ditangani oleh Spring.

@Autowired
private MappingMongoConverter converter;

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

Jika Json Anda yang disimpan adalah:

{ "axxxb" : "value" }

Melalui Spring (MongoClient) akan dibaca sebagai:

{ "a.b" : "value" }

membutuhkan kacang jenis 'org.springframework.data.mongodb.core.convert.MappingMongoConverter' yang tidak dapat ditemukan.
Sathya Narayan C

1

Saya menggunakan escaping berikut di JavaScript untuk setiap kunci objek:

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

Yang saya suka dari ini adalah ia hanya menggantikan $di awal, dan tidak menggunakan karakter unicode yang bisa rumit untuk digunakan di konsol. _bagi saya jauh lebih mudah dibaca daripada karakter unicode. Ini juga tidak mengganti satu set karakter khusus ( $, .) dengan yang lain (unicode). Tapi lolos dengan benar dengan tradisional \.


3
Dan jika seseorang menggunakan _ di salah satu kunci mereka, Anda akan mendapatkan bug.
BT

1

Tidak sempurna, tetapi akan berfungsi dalam banyak situasi: gantilah karakter terlarang dengan yang lain. Karena ada dalam kunci, karakter baru ini seharusnya cukup langka.

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

Ini tesnya:

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

dan hasilnya - perhatikan bahwa nilainya tidak diubah:

{
  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

Ada beberapa cara buruk untuk membuat kueri yang tidak disarankan untuk digunakan dalam aplikasi daripada untuk tujuan debug (hanya berfungsi pada objek yang disematkan):

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

Seperti yang disebutkan pengguna lain, encoding / decoding ini dapat menjadi masalah di masa mendatang, jadi mungkin lebih mudah untuk mengganti semua kunci yang memiliki titik. Ini adalah fungsi rekursif yang saya buat untuk mengganti kunci dengan '.' kejadian:

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)

Anda juga dapat mengubah kode ini untuk mengganti '$', karena itu adalah karakter lain yang tidak diizinkan mongo dalam sebuah kunci.


0

Untuk PHP saya mengganti nilai HTML untuk periode tersebut. Begitulah ".".

Ini menyimpan di MongoDB seperti ini:

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

dan kode PHP ...

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

0

Pasangan Lodash akan memungkinkan Anda untuk berubah

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

ke

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

menggunakan

var newObj = _.pairs(oldObj);

0

Anda dapat menyimpannya apa adanya dan mengubahnya menjadi cantik setelahnya

Saya menulis contoh ini di Livescript. Anda dapat menggunakan situs web Livescript.net untuk mengevaluasi itu

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

Itu akan menghasilkan

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


0

Beri Anda tip saya: Anda dapat menggunakan JSON.stringify untuk menyimpan Objek / Array berisi nama kunci memiliki titik, lalu parsing string ke Objek dengan JSON.parse untuk diproses saat mendapatkan data dari database

Solusi lain: Atur ulang skema Anda seperti:

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

0

MongoDB terbaru mendukung kunci dengan titik, tetapi java MongoDB-driver tidak mendukung. Jadi untuk membuatnya bekerja di Java, saya menarik kode dari github repo java-mongo-driver dan membuat perubahan sesuai dengan fungsi kunci isValid mereka, membuat jar baru darinya, menggunakannya sekarang.


0

Ganti titik ( .) atau dolar ( $) dengan karakter lain yang tidak akan pernah digunakan dalam dokumen sebenarnya. Dan kembalikan titik ( .) atau dolar ( $) saat mengambil dokumen. Strategi tersebut tidak akan memengaruhi data yang dibaca pengguna.

Anda dapat memilih karakter dari semua karakter .


0

Anehnya, dengan menggunakan mongojs, saya dapat membuat dokumen dengan titik jika saya menyetel _id sendiri, namun saya tidak dapat membuat dokumen saat _id dibuat:

Berhasil:

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

Tidak bekerja:

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

Saya pertama kali berpikir bahwa memperbarui dokumen dengan kunci titik juga berfungsi, tetapi itu mengidentifikasi titik sebagai subkunci!

Melihat bagaimana mongojs menangani titik (subkunci), saya akan memastikan kunci saya tidak mengandung titik.


0

Seperti yang @JohnnyHK sebutkan, hapus tanda baca atau '.' dari kunci Anda karena ini akan menciptakan masalah yang jauh lebih besar ketika data Anda mulai terakumulasi menjadi kumpulan data yang lebih besar. Ini akan menyebabkan masalah terutama ketika Anda memanggil operator agregat seperti $ merge yang memerlukan akses dan perbandingan kunci yang akan menimbulkan kesalahan. Saya telah mempelajarinya dengan cara yang sulit, tolong jangan ulangi untuk mereka yang baru memulai.


-2

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

Ditemukan dalam pesan kesalahan. Jika Anda menggunakan anaconda(temukan file koresponden jika tidak), cukup ubah nilainya dari check_keys = Truemenjadi Falsedalam file yang disebutkan di atas. Itu akan berhasil!

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.