Wygląda na to, że mongo nie pozwala na wstawianie kluczy z kropką (.) Lub znakiem dolara ($), jednak kiedy zaimportowałem plik JSON zawierający kropkę za pomocą narzędzia mongoimport, działało dobrze. Kierowca narzeka na próbę wstawienia tego elementu.
Tak wygląda dokument w bazie danych:
{
"_id": {
"$oid": "..."
},
"make": "saab",
"models": {
"9.7x": [
2007,
2008,
2009,
2010
]
}
}
Czy robię to wszystko źle i nie powinienem używać takich map skrótów z danymi zewnętrznymi (tj. Modelami), czy też mogę jakoś uciec przed kropką? Może myślę za dużo w stylu Javascript.
javascript
mongodb
nosql
Michael Yagudaev
źródło
źródło
Odpowiedzi:
MongoDB nie obsługuje kluczy z kropką, więc będziesz musiał wstępnie przetworzyć plik JSON, aby go usunąć / zamienić przed zaimportowaniem go, w przeciwnym razie będziesz musiał ustawić się na różnego rodzaju problemy.
Nie ma standardowego obejścia tego problemu, najlepsze podejście jest zbyt zależne od specyfiki sytuacji. Ale unikałbym jakiegokolwiek podejścia do kodowania / dekodowania klucza, jeśli to możliwe, ponieważ będziesz nadal płacić niedogodności związane z tym w nieskończoność, gdzie restrukturyzacja JSON prawdopodobnie byłaby jednorazowym kosztem.
źródło
models
tutaj), oraz (c) nie musimy odpytywać ich według nazwy klucza w Mongo. Tak więc wzorzec, na którym się zdecydowałem, toJSON.stringify
to pole przy zapisywaniu i „JSON.parse” podczas pobierania.Jak wspomniano w innych odpowiedziach, MongoDB nie zezwala na znaki
$
lub.
jako klucze mapowania ze względu na ograniczenia dotyczące nazw pól . Jednak, jak wspomniano w sekcji Operator znaku dolara, uniknięcie tego ograniczenia nie zapobiega wstawianiu dokumentów z takimi kluczami, a jedynie zapobiega ich aktualizowaniu lub odpytywaniu.Problem po prostu zastępując
.
z[dot]
lubU+FF0E
(jak wspomniano w innym miejscu na tej stronie) jest to, co się dzieje, gdy użytkownik chce legalnie przechowywania klucza[dot]
lubU+FF0E
?Podejściem, które stosuje sterownik afMorphia Fantoma , polega na użyciu sekwencji ucieczki Unicode podobnych do tej w Javie, ale najpierw należy upewnić się, że znak ucieczki jest unikany. Zasadniczo dokonywane są następujące zamiany ciągów (*):
Odwrotna zamiana jest wykonywana, gdy klucze map są następnie odczytywane z MongoDB.
Lub w kodzie Fantoma :
Str encodeKey(Str key) { return key.replace("\\", "\\\\").replace("\$", "\\u0024").replace(".", "\\u002e") } Str decodeKey(Str key) { return key.replace("\\u002e", ".").replace("\\u0024", "\$").replace("\\\\", "\\") }
Jedynym przypadkiem, w którym użytkownik musi być świadomy takich konwersji, jest tworzenie zapytań dla takich kluczy.
Biorąc pod uwagę, że przechowywanie
dotted.property.names
w bazach danych do celów konfiguracyjnych jest powszechne, uważam, że takie podejście jest lepsze niż po prostu blokowanie wszystkich takich kluczy map.(*) afMorphia faktycznie wykonuje pełne / poprawne reguły ucieczki unicode, jak wspomniano w składni ucieczki Unicode w Javie, ale opisana sekwencja zamiany działa równie dobrze.
źródło
//g
zastąpić wszystkie wystąpienia, a nie tylko pierwsze. Również użycie odpowiedników o pełnej szerokości, jak w odpowiedzi Martina Konecnego, wydaje się być dobrym pomysłem. Na koniec do kodowania wystarczy jeden lewy ukośnik.key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
U+FF04
.Dokumentacja Mongo sugeruje zastąpienie niedozwolonych znaków, takich jak
$
i.
ich odpowiednikami w Unicode.źródło
db.test.insert({"field\uff0ename": "test"})
Najnowsza stabilna wersja (v3.6.1) bazy danych MongoDB obsługuje teraz kropki (.) W kluczach lub nazwach pól.
Nazwy pól mogą teraz zawierać kropki (.) I dolar ($)
źródło
mongoClient.getDatabase("mydb").getCollection("test").insertOne(new Document("value", new Document("key.with.dots", "value").append("$dollar", "value")));
wykonać następujące polecenie: Błąd przy użyciu sterownika mongodb.3.6.3 i MongoDB 3.6.3.mongodb-4.1.1
ipymongo-3.7.1
. Mogę dodać dokumenty zawierające klucze z.
robomongo, ale nie zpymongo
, nadal się podnosi Żałuję , żeInvalidDocument: key '1.1' must not contain '.'
nie zostało to już naprawione ...Rozwiązanie, które właśnie zaimplementowałem, z którego jestem naprawdę zadowolony, polega na podzieleniu nazwy klucza i wartości na dwa oddzielne pola. W ten sposób mogę zachować postacie dokładnie takie same i nie martwić się żadnym z tych koszmarów związanych z analizą. Dokument wyglądałby następująco:
{ ... keyName: "domain.com", keyValue: "unregistered", ... }
Nadal możesz zapytać o to dość łatwo, po prostu wykonując a
find
na polach keyName i keyValue .Więc zamiast:
db.collection.find({"domain.com":"unregistered"})
który w rzeczywistości nie zadziała zgodnie z oczekiwaniami, uruchomisz:
db.collection.find({keyName:"domain.com", keyValue:"unregistered"})
i zwróci oczekiwany dokument.
źródło
Możesz spróbować użyć skrótu w kluczu zamiast wartości, a następnie zapisać tę wartość w wartości 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 ] }
Następnie uzyskasz dostęp do modeli przy użyciu skrótu później.
var version = "9.7x"; collection.find( { _id : ...}, function(e, data ) { var models = data.models[ md5(version) ]; }
źródło
Jest teraz obsługiwany
MongoDb 3.6 i nowsze obsługują zarówno kropki, jak i dolary w nazwach pól. Zobacz poniżej JIRA: https://jira.mongodb.org/browse/JAVA-2810
Aktualizacja Mongodb do wersji 3.6+ brzmi jak najlepszy sposób.
źródło
Z dokumentów MongoDB „the”. znak nie może występować nigdzie w nazwie klucza ”. Wygląda na to, że będziesz musiał wymyślić schemat kodowania lub obejść się bez niego.
źródło
Musisz uciec od kluczy. Ponieważ wydaje się, że większość ludzi nie wie, jak prawidłowo uciec od ciągów, oto kroki:
Pamiętaj też, że mongo również nie pozwala, aby klawisze zaczynały się od '$', więc musisz tam zrobić coś podobnego
Oto kod, który to robi:
// 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, '~') }
źródło
Późna odpowiedź, ale jeśli używasz Spring i Mongo, Spring może zarządzać konwersją za Ciebie za pomocą
MappingMongoConverter
. To rozwiązanie firmy JohnnyHK, ale obsługiwane przez Spring.@Autowired private MappingMongoConverter converter; @PostConstruct public void configureMongo() { converter.setMapKeyDotReplacement("xxx"); }
Jeśli zapisany plik Json to:
{ "axxxb" : "value" }
Do wiosny (MongoClient) będzie czytane jako:
{ "a.b" : "value" }
źródło
Używam następujących znaków ucieczki w JavaScript dla każdego klucza obiektu:
key.replace(/\\/g, '\\\\').replace(/^\$/, '\\$').replace(/\./g, '\\_')
Podoba mi się to, że zastępuje tylko
$
na początku i nie używa znaków Unicode, które mogą być trudne w użyciu w konsoli._
jest dla mnie o wiele bardziej czytelny niż znak Unicode. Nie zastępuje również jednego zestawu znaków specjalnych ($
,.
) innym (unicode). Ale właściwie ucieka z tradycyjnym\
.źródło
Nie jest idealny, ale zadziała w większości sytuacji: zastąp niedozwolone znaki czymś innym. Ponieważ jest w kluczach, te nowe znaki powinny być dość rzadkie.
/** 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 }
Oto test:
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) )
a wyniki - zwróć uwagę, że wartości nie są modyfikowane:
{ 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": {} } }
źródło
Jest jakiś brzydki sposób na zapytanie, które nie jest zalecane, aby używać go w aplikacji, a nie do celów debugowania (działa tylko na obiektach osadzonych):
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 ])
źródło
Jak wspomniał inny użytkownik, kodowanie / dekodowanie może stać się problematyczne w przyszłości, więc prawdopodobnie łatwiej będzie zastąpić wszystkie klucze, które mają kropkę. Oto rekurencyjna funkcja, którą utworzyłem, aby zastąpić klucze znakiem „.” wystąpienia:
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)
Możesz zmodyfikować ten kod, aby zastąpić '$', ponieważ jest to kolejny znak, na który mongo nie zezwala w kluczu.
źródło
W przypadku PHP podstawiam wartość HTML dla kropki. To jest
"."
.Przechowuje w MongoDB w ten sposób:
"validations" : { "4e25adbb1b0a55400e030000" : { "associate" : "true" }, "4e25adb11b0a55400e010000" : { "associate" : "true" } }
i kod PHP ...
$entry = array('associate' => $associate); $data = array( '$set' => array( 'validations.' . str_replace(".", `"."`, $validation) => $entry )); $newstatus = $collection->update($key, $data, $options);
źródło
Pary Lodash pozwolą Ci na zmianę
{ 'connect.sid': 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' }
w
[ [ 'connect.sid', 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' ] ]
za pomocą
var newObj = _.pairs(oldObj);
źródło
Możesz go przechowywać tak, jak jest, a potem przekonwertować na ładny
Napisałem ten przykład na Livescript. Możesz użyć strony livescript.net, aby to ocenić
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
Będzie produkować
{"field.field1":1, "field.field2":2, "field.field3":5, "field.nested.more":1, "field.nested.moresdafasdf":23423, "field3":3}
źródło
Daję ci moją wskazówkę: możesz użyć JSON.stringify do zapisania obiektu / tablicy zawierającej nazwę klucza z kropkami, a następnie przeanalizuj ciąg do Object za pomocą JSON.parse, aby przetworzyć, gdy pobierzesz dane z bazy danych
Inne obejście: Zmień strukturę schematu, na przykład:
key : { "keyName": "a.b" "value": [Array] }
źródło
Najnowsza baza danych MongoDB obsługuje klawisze z kropką, ale sterownik java MongoDB nie obsługuje. Aby to działało w Javie, wyciągnąłem kod z repozytorium github sterownika java-mongo i dokonałem odpowiednich zmian w ich funkcji isValid Key, stworzyłem z niego nowy jar, używając go teraz.
źródło
Zastąp kropkę (
.
) lub dolara ($
) innymi znakami, które nigdy nie będą używane w prawdziwym dokumencie. I przywróć kropkę (.
) lub dolara ($
) podczas pobierania dokumentu. Strategia nie wpłynie na dane czytane przez użytkownika.Możesz wybrać postać spośród wszystkich znaków .
źródło
Dziwne jest to, że używając mongojs, mogę utworzyć dokument z kropką, jeśli sam ustawię _id, jednak nie mogę utworzyć dokumentu, gdy jest generowany _id:
Działa:
db.testcollection.save({"_id": "testdocument", "dot.ted.": "value"}, (err, res) => { console.log(err, res); });
Nie działa:
db.testcollection.save({"dot.ted": "value"}, (err, res) => { console.log(err, res); });
Najpierw pomyślałem, że aktualizacja dokumentu za pomocą klawisza z kropką również zadziałała, ale identyfikuje kropkę jako podklucz!
Widząc, jak mongojs obsługuje kropkę (podklucz), upewnię się, że moje klucze nie zawierają kropki.
źródło
Jak wspomniał @JohnnyHK , usuń znaki interpunkcyjne lub „.” z kluczy, ponieważ spowoduje to znacznie większe problemy, gdy dane zaczną gromadzić się w większym zbiorze danych. Spowoduje to problemy, zwłaszcza w przypadku wywoływania operatorów agregujących, takich jak $ merge, które wymagają dostępu i porównania kluczy, co spowoduje błąd. Nauczyłem się tego na własnej skórze, proszę nie powtarzać tym, którzy zaczynają.
źródło
/home/user/anaconda3/lib/python3.6/site-packages/pymongo/collection.py
Znalazłem to w komunikatach o błędach. Jeśli używasz
anaconda
(znajdź odpowiedni plik, jeśli nie), po prostu zmień wartość zcheck_keys = True
naFalse
w powyższym pliku. To zadziała!źródło