Крапка MongoDB (.) У назві ключа


95

Здається, mongo не дозволяє вставляти ключі з крапкою (.) Або знаком долара ($), однак, коли я імпортував файл JSON, який містив крапку за допомогою інструмента mongoimport, він працював нормально. Водій скаржиться на спробу вставити цей елемент.

Ось як виглядає документ у базі даних:

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

Я все це роблю неправильно і не повинен використовувати подібні хеш-карти із зовнішніми даними (тобто моделями), чи я можу якось уникнути крапки? Можливо, я занадто багато думаю про Javascript.


Варто подивитися, npmjs.com/package/mongo-escape
Сем Денті

Відповіді:


87

MongoDB не підтримує ключі з крапкою, тому вам доведеться попередньо обробити ваш файл JSON, щоб видалити / замінити їх перед імпортуванням, інакше ви налаштуєтеся на всілякі проблеми.

Існує не звичайне вирішення цього питання, найкращий підхід занадто залежить від особливостей ситуації. Але я б уникав будь-якого підходу до кодування / декодування ключів, якщо це можливо, оскільки ви продовжуватимете платити за незручності постійно, коли реструктуризація JSON, мабуть, буде одноразовою вартістю.


1
Я не думаю, що існує стандартний спосіб, найкращий підхід занадто залежить від особливостей ситуації. Але я б уникав будь-якого підходу до кодування / декодування ключів, якщо це можливо, оскільки ви продовжуватимете платити за незручності постійно, коли реструктуризація JSON, мабуть, буде одноразовою вартістю.
JohnnyHK

8
Знову натрапив на цю ситуацію. Здається, це трапляється не стільки з іменами ключів програми, якими ми можемо керувати, і нам часто потрібно робити запити, скільки з переданими користувачем даними у вкладених структурах даних, які ми не можемо контролювати, але (а) хотіли б зберігати в Монго , (b) ми знаємо, в яких конкретних полях це може трапитися (наприклад, modelsтут), і (c) нам не потрібно; нам не потрібно запитувати їх за назвою ключа в Монго. Отже, шаблон, на якому я зупинився, - JSON.stringifyце поле для збереження та „JSON.parse“ при отриманні.
прототип

16
Якщо потрібно, ви можете надати опцію {check_keys: false} для обходу цієї проблеми.
Tzury Bar Yochay

5
@TzuryBarYochay OMG ви знайшли еквівалент MongoDB північно-західному проходу. Я думаю, що це має бути прийнятою відповіддю.
прототип

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

22

Як згадувалося в інших відповідях, MongoDB не дозволяє $або .символи як ключі карти через обмеження імен полів . Однак, як згадувалося в " Операторі знаків долара", уникнення цього обмеження не заважає вам вставляти документи з такими ключами, воно просто заважає вам оновлювати або запитувати їх.

Проблема простої заміни .на [dot]або U+FF0E(як згадувалося десь на цій сторінці) полягає в тому, що відбувається, коли користувач законно хоче зберегти ключ [dot]чи U+FF0E?

Підхід, який використовує драйвер afMorphia від Fantom , полягає у використанні послідовностей вхідного коду Unicode, схожих на послідовності Java, але забезпечення того, щоб спершу було введено символ втечі. По суті, виконуються такі заміни рядків (*):

\  -->  \\
$  -->  \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, як згадується в синтаксисі Unicode escape в Java, але описана послідовність заміни працює так само добре.


Потрібно використовувати //gдля заміни всіх випадків, а не лише перших. Крім того, використання еквівалентів на всю ширину, як у відповіді Мартіна Конечні, видається гарною ідеєю. Нарешті, для кодування достатньо однієї косої риски. key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
cw '

1
@cw '- Код наведений у Java, як синтаксис, тому replace насправді замінює всі випадки, а для уникнення зворотних скісних рисок потрібні подвійні зворотні скісні риски. І знову вам потрібно запровадити якусь форму втечі, щоб забезпечити охоплення всіх справ. Хтось, у якийсь час, може насправді захотіти ключ від U+FF04.
Стів Ейнон,

2
Як виявляється, Mongodb підтримує крапки та долари в останніх версіях. Дивіться: - stackoverflow.com/a/57106679/3515086
Abhidemon

18

Документи Mongo пропонують замінити нелегальні символи, такі як $і .їхні еквіваленти в Unicode.

У цих ситуаціях ключі повинні замінити зарезервовані $ і. символів. Будь-якого символу достатньо, але подумайте про використання еквівалентів повної ширини Unicode: U + FF04 (тобто “$”) та U + FF0E (тобто “.”).


74
Це звучить як рецепт масових головних болів налагодження.
ніхто

2
@AndrewMedico, @tamlyn - Я думаю, що документи означають щось на кшталтdb.test.insert({"field\uff0ename": "test"})
P. Myer Nore

4
-1 А. Це жахлива ідея - а що, якщо хтось насправді намагається використовувати ці символи Юнікоду як ключ? Тоді у вас виникає тиха помилка, яка зробить хто знає що у вашій системі. Не використовуйте неоднозначні методи втечі подібними. Б. у монго-документах більше про це не говорять, мабуть, тому, що хтось усвідомив її жахливу ідею
BT

7
@SergioTulentsev Я попросив їх видалити рекомендацію :) github.com/mongodb/docs/commit/…
BT

2
@BT: капелюх вам, сер :)
Серхіо Туленцев

15

Остання стабільна версія (v3.6.1) MongoDB зараз підтримує крапки (.) У ключах або іменах полів.

Назви полів тепер можуть містити крапки (.) Та символи долара ($)


10
Навіть якщо сервер підтримує це зараз, драйвер все одно перевіряє наявність $ та крапок у ключах і не приймає їх. Тому Монго лише теоретично підтримує крапки та символи долара. Практично це ще не можна використовувати :(
JMax,

Можливо, ви використовуєте якийсь старий або несумісний клієнт. Я використовую це на своїх виробничих серверах без будь-якого поту. Я перевірив наявність клієнтів NodeJS та Java.
h4ck3d

З 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.
JMax

1
Справді, я щойно спробував з установкою mongodb-4.1.1і pymongo-3.7.1. Я можу додати документи, що містять ключі з за .допомогою robomongo, але не від pymongo, він піднімає підвіконня Побажаю , що InvalidDocument: key '1.1' must not contain '.'це вже виправлено ...
Навчання - безлад

Я спробував із сервером mongodb 4.0.9 та драйвером Java 3.10.2, але він не приймає крапки в назві ключа. дивно, що коли спробуєш, що за допомогою robomongo це працює ...
xyzt

12

Рішення, яке я щойно реалізував і яким я справді задоволений, включає розділення імені та значення ключа на два окремі поля. Таким чином, я можу зберегти персонажів абсолютно однаковими, і не турбуватися про те, що розбирає кошмари. Документ мав би виглядати так:

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

Ви все ще можете зробити це досить просто, просто зробивши a findу полях keyName і keyValue .

Отже, замість:

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

що насправді не спрацювало б, як очікувалося, ви б запустили:

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

і він поверне очікуваний документ.


Як ти це зробив? Не могли б ви допомогти мені у цій самій справі?
профайлер

Я додав приклад запиту. Це допомагає?
Стів

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) ];
}

1
Мені подобається це, чисте рішення з одностороннім перемішуванням і дуже схоже на те, як все працює під капотом.
Михайло Ягудаєв

3
Проблема використання хешів як ключів полягає в тому, що вони не гарантовано є унікальними, і вони часто спричиняють зіткнення . Плюс обчислення криптографічного хешу кожного разу, коли ви хочете отримати доступ до карти, мені здається не найоптимальнішим рішенням.
Steve Eynon

2
Чому це краще, ніж замінити крапку спеціальним символом або послідовністю?
B Сім

Перетворення рядків у base64 набагато краще.
Zen

8

Це підтримується зараз

MongoDb 3.6 і далі підтримує як крапки, так і долари в назвах полів. Дивіться нижче JIRA: https://jira.mongodb.org/browse/JAVA-2810

Оновлення вашого Mongodb до 3.6+ звучить найкращим способом.


Це найкраща відповідь тут. : +1
hello_abhishek

3
3.6 може зберігати їх, але це ще не підтримується, може спричиняти помилки драйвера та може порушувати запит / оновлення: обмеження : "Мова запитів MongoDB не завжди може суттєво виражати запити щодо документів, імена полів яких містять ці символи (див. SERVER- 30575). Поки підтримка не додається мовою запитів, використання $ та. В іменах полів не рекомендується і не підтримується офіційними драйверами MongoDB. "
JeremyDouglass


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

Це екранування все одно може зламатися, якщо у вас є рядки типу ". ~ P.". Тут рядок, що виділяється, буде '~ p ~~ p ~ p'. Ескапірування дасть вам ". ~ ..", яке відрізняється від фактичного рядка.
jvc

1
@jvc Ви маєте рацію! Я виправив пояснення та приклади функцій екранування. Повідомте мене, якщо вони все ще зламані!
BT

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" }

потрібен компонент типу 'org.springframework.data.mongodb.core.convert.MappingMongoConverter', який не вдалося знайти.
Sathya Narayan C

1

Я використовую наступне екранування в JavaScript для кожного об’єктного ключа:

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

Мені подобається, що він замінює лише $на початку, і в ньому не використовуються символи Unicode, які може бути складно використовувати в консолі. _для мене набагато читабельніший, ніж символ Unicode. Він також не замінює один набір спеціальних символів ( $, .) іншим (unicode). Але належним чином рятується від традиційного \.


3
І якщо хтось використовує _ в будь-якому з своїх ключів, ви отримаєте помилки.
BT

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": {}
  }
}

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

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 не допускає в ключ.


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

0

Пари Лодаш дозволять вам змінитися

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

в

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

використання

var newObj = _.pairs(oldObj);

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}


0

Дайте мою підказку: Ви можете використовувати JSON.stringify для збереження об’єкта / масиву, що містить ім’я ключа, крапки, а потім проаналізувати рядок для об’єкта за допомогою JSON.parse для обробки при отриманні даних з бази даних

Ще одне обхідне рішення: реструктуруйте свою схему, наприклад:

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

0

Останній MongoDB підтримує клавіші з крапкою, але Java-драйвер MongoDB не підтримує. Тож, щоб це працювало на Java, я витягнув код із репозиторію github java-mongo-driver та вніс відповідні зміни у їхню функцію isValid Key, створив з неї нову банку, використовуючи її зараз.


0

Замініть крапку ( .) або долар ( $) іншими символами, які ніколи не будуть використовуватися в реальному документі. І відновіть крапку ( .) або долар ( $) під час отримання документа. Стратегія не впливатиме на дані, які читає користувач.

Ви можете вибрати персонажа з усіх символів .


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

Спочатку я думав, що оновлення документа за допомогою крапкового ключа також спрацювало, але його ідентифікація крапки як підрозділу!

Побачивши, як mongojs обробляє крапку (підрозділ), я переконаюсь, що мої ключі не містять крапки.


0

Як і те, що згадав @JohnnyHK , видаліть пунктуацію або '.' з ваших ключів, оскільки це створить набагато більші проблеми, коли ваші дані почнуть накопичуватися у більший набір даних. Це спричинить проблеми, особливо коли ви викликаєте агреговані оператори, такі як $ merge, який вимагає доступу та порівняння ключів, що призведе до помилки. Я навчився цього важким способом, будь ласка, не повторюйте для тих, хто починає.


-2

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

Знайшов у повідомленнях про помилки. Якщо ви використовуєте anaconda(знайдіть файл-кореспондент, якщо ні), просто змініть значення з check_keys = Trueна Falseу файлі, зазначеному вище. Це спрацює!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.