MongoDB punto (.) Nel nome della chiave


94

Sembra che mongo non consenta l'inserimento di chiavi con un punto (.) O un segno di dollaro ($), tuttavia quando ho importato un file JSON che conteneva un punto utilizzando lo strumento mongoimport ha funzionato bene. L'autista si lamenta del tentativo di inserire quell'elemento.

Ecco come appare il documento nel database:

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

Sto sbagliando tutto questo e non dovrei usare mappe hash come quella con dati esterni (cioè i modelli) o posso sfuggire al punto in qualche modo? Forse sto pensando troppo come Javascript.


Vale la pena guardare, npmjs.com/package/mongo-escape
Sam Denty,

Risposte:


85

MongoDB non supporta le chiavi con un punto , quindi dovrai preelaborare il tuo file JSON per rimuoverli / sostituirli prima di importarlo o ti preparerai per tutti i tipi di problemi.

Non esiste una soluzione alternativa standard a questo problema, l'approccio migliore dipende troppo dalle specifiche della situazione. Ma eviterei qualsiasi approccio codificatore / decodificatore chiave, se possibile, poiché continuerai a pagare l'inconveniente di quello in perpetuo, dove una ristrutturazione JSON sarebbe presumibilmente un costo una tantum.


1
Non credo che ci sia un modo standard, l'approccio migliore dipende troppo dalle specifiche della situazione. Ma eviterei qualsiasi approccio codificatore / decodificatore chiave, se possibile, poiché continuerai a pagare l'inconveniente di quello in perpetuo, dove una ristrutturazione JSON sarebbe presumibilmente un costo una tantum.
JohnnyHK

8
Mi sono imbattuto di nuovo in questa situazione. Ciò sembra accadere non tanto con i nomi delle chiavi delle app, che possiamo controllare e spesso dobbiamo interrogare, ma con i dati forniti dall'utente in strutture di dati annidate, che non possiamo controllare, ma (a) vorremmo memorizzare in Mongo , (b) sappiamo in quali campi specifici ciò potrebbe accadere (ad esempio modelsqui), e (c) non abbiamo bisogno di interrogarli per nome chiave in Mongo. Quindi uno schema su cui ho deciso è JSON.stringifyquesto campo al salvataggio e "JSON.parse" al recupero.
prototipo

16
Se è necessario, puoi fornire l'opzione {check_keys: false} per aggirare questo problema.
Tzury Bar Yochay

5
@TzuryBarYochay OMG hai trovato l'equivalente MongoDB del passaggio a nord-ovest. Penso che questa dovrebbe essere la risposta accettata.
prototipo

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

22

Come già detto in altre risposte MongoDB non permette $o .caratteri come chiavi mappa a causa di restrizioni sui nomi dei campi . Tuttavia, come menzionato in Dollar Sign Operator Escaping questa restrizione non ti impedisce di inserire documenti con tali chiavi, ti impedisce solo di aggiornarli o interrogarli.

Il problema della semplice sostituzione .con [dot]o U+FF0E(come menzionato altrove in questa pagina) è cosa succede quando l'utente vuole legittimamente memorizzare la chiave[dot] o U+FF0E?

Un approccio che il pilota afMorphia di Fantom consiste nell'usare sequenze di escape unicode simili a quelle di Java, ma assicurandosi che il carattere di escape venga prima evitato. In sostanza, vengono effettuate le seguenti sostituzioni di stringa (*):

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

Una sostituzione inversa viene effettuata quando le chiavi della mappa vengono successivamente lette da MongoDB.

O in codice Fantom :

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

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

L'unica volta che un utente deve essere consapevole di tali conversioni è quando costruisce query per tali chiavi.

Dato che è comune memorizzare dotted.property.names nei database per scopi di configurazione, credo che questo approccio sia preferibile al semplice divieto di tutte queste chiavi di mappa.

(*) afMorphia esegue effettivamente le regole di escape Unicode complete / appropriate come menzionato nella sintassi di escape Unicode in Java, ma la sequenza di sostituzione descritta funziona altrettanto bene.


Dovrebbe utilizzare //gper sostituire tutte le occorrenze e non solo la prima. Inoltre, l'utilizzo degli equivalenti a larghezza intera come nella risposta di Martin Konecny ​​sembra essere una buona idea. Infine, una barra rovesciata è sufficiente per la codifica. key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
cw '

1
@cw '- Il codice è in una sintassi simile a Java, quindi sostituire in realtà sostituisce tutte le occorrenze e sono necessari doppi backslash per l'escape dei backslash. E ancora, è necessario introdurre una qualche forma di fuga per garantire che tutti i casi siano coperti. Qualcuno, a un certo punto, potrebbe effettivamente volere una chiave di U+FF04.
Steve Eynon

2
A quanto pare, Mongodb supporta punti e dollari nelle sue ultime versioni. Vedere: - stackoverflow.com/a/57106679/3515086
Abhidemon

18

I documenti di Mongo suggeriscono di sostituire caratteri illegali come $e .con i loro equivalenti Unicode.

In queste situazioni, le chiavi dovranno sostituire i $ e. personaggi. Qualsiasi carattere è sufficiente, ma considera l'utilizzo degli equivalenti a larghezza intera Unicode: U + FF04 (cioè "$") e U + FF0E (cioè ".").


74
Sembra una ricetta per enormi mal di testa di debug lungo la strada.
nessuno

2
@AndrewMedico, @tamlyn - Penso che i documenti abbiano un significato simile adb.test.insert({"field\uff0ename": "test"})
P. Myer Nore,

4
-1 R. È un'idea terribile: e se qualcuno stesse effettivamente cercando di usare quei caratteri Unicode come chiave? Quindi hai un errore silenzioso che farà chissà cosa al tuo sistema. Non utilizzare metodi di escape ambigui come quello. B. i mongo docs non lo dicono più, probabilmente perché qualcuno ha realizzato che è un'idea terribile
BT

7
@SergioTulentsev li ho convinti a rimuovere la raccomandazione :) github.com/mongodb/docs/commit/…
BT

2
@BT: la punta di cappello a te, signore :)
Sergio Tulentsev

15

L'ultima versione stabile (v3.6.1) di MongoDB ora supporta i punti (.) Nelle chiavi o nei nomi dei campi.

I nomi dei campi possono ora contenere punti (.) E caratteri dollaro ($)


10
Anche se il server lo supporta ora, il driver controlla comunque la presenza di $ e punti nelle chiavi e non li accetta. Pertanto Mongo supporta solo teoricamente punti e caratteri dollaro. Praticamente questo non è ancora utilizzabile :(
JMax

Forse stai usando un client vecchio o incompatibile. L'ho usato sui miei server di produzione senza alcun problema. Ho verificato la presenza di client NodeJS e Java.
h4ck3d

Con Java sicuramente non funziona! Prova il seguente comando: mongoClient.getDatabase("mydb").getCollection("test").insertOne(new Document("value", new Document("key.with.dots", "value").append("$dollar", "value")));non riesce utilizzando mongodb-driver.3.6.3 e MongoDB 3.6.3.
JMax

1
In effetti, ho appena provato con una configurazione mongodb-4.1.1e pymongo-3.7.1. Posso aggiungere documenti contenenti chiavi con .robomongo ma non da pymongo, solleva ancora InvalidDocument: key '1.1' must not contain '.'Vorrei che fosse stato risolto ormai ...
apprendimento è un casino

Ho provato con mongodb server 4.0.9 e java driver 3.10.2 ma non accetta il punto nel nome della chiave. è strano che quando lo provi usando robomongo funziona ...
xyzt

12

Una soluzione che ho appena implementato e di cui sono davvero soddisfatto prevede la divisione del nome e del valore della chiave in due campi separati. In questo modo, posso mantenere i personaggi esattamente gli stessi e non preoccuparmi di nessuno di quegli incubi di analisi. Il documento sarebbe simile a:

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

È ancora possibile eseguire una query in modo abbastanza semplice, semplicemente eseguendo un findsui campi keyName e keyValue .

Quindi invece di:

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

che in realtà non funzionerebbe come previsto, dovresti eseguire:

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

e restituirà il documento atteso.


Come l'hai fatto? Per favore, potresti aiutarmi con lo stesso caso.
profiler

Ho aggiunto un esempio di query. Questo aiuta?
Steve

10

Puoi provare a utilizzare un hash nella chiave invece del valore, quindi archiviare tale valore nel valore 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
    ]
}

Potrai quindi accedere ai modelli utilizzando l'hash in seguito.

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

1
Mi piace questa soluzione pulita con hashing a 1 via e molto simile al modo in cui funzionano le cose sotto il cofano.
Michael Yagudaev

3
Il problema con l'utilizzo di hash come chiavi è che non è garantito che siano univoci e spesso producono collisioni . Inoltre, calcolare un hash crittografico ogni volta che si desidera accedere a una mappa non mi sembra la soluzione ottimale.
Steve Eynon

2
Perché è meglio che sostituire il punto con un carattere o una sequenza speciale?
B Seven,

La conversione di stringhe in base64 è molto meglio.
Zen

8

Ora è supportato

MongoDb 3.6 in poi supporta sia i punti che il dollaro nei nomi dei campi. Vedi sotto JIRA: https://jira.mongodb.org/browse/JAVA-2810

L'aggiornamento del tuo Mongodb a 3.6+ sembra il modo migliore per andare.


Questa è la migliore risposta qui. : +1
hello_abhishek

3
3.6 li può memorizzare, sì, ma è non è ancora supportato, può generare errori del guidatore, e possono rompersi interrogazione / aggiornamenti: restrizioni : "La Query Language MongoDB non può sempre significato esprimere interrogazioni su documenti i cui nomi campo contenere questi caratteri (vedi su server 30575). Fino a quando non verrà aggiunto il supporto nel linguaggio di query, l'uso di $ e. Nei nomi dei campi non è consigliato e non è supportato dai driver MongoDB ufficiali. "
JeremyDouglass

4

Dai documenti di MongoDB "il file". " il carattere non deve apparire da nessuna parte nel nome della chiave ". Sembra che dovrai inventare uno schema di codifica o farne a meno.


4

Avrai bisogno di sfuggire alle chiavi. Poiché sembra che la maggior parte delle persone non sappia come eseguire correttamente l'escape delle stringhe, ecco i passaggi:

  1. scegli un carattere di fuga (meglio scegliere un carattere usato raramente). Per esempio. '~'
  2. Per uscire, prima sostituisci tutte le istanze del carattere di escape con una sequenza preceduta dal tuo carattere di escape (ad es. '~' -> '~ t'), quindi sostituisci qualsiasi carattere o sequenza di cui hai bisogno per uscire con una sequenza preceduta dal tuo carattere di escape . Per esempio. "." -> "~ p"
  3. Per annullare l'escape, prima rimuovi la sequenza di escape da tutte le istanze della seconda sequenza di escape (ad esempio '~ p' -> '.'), Quindi trasforma la sequenza di caratteri di escape in un singolo carattere di escape (ad esempio '~ s' -> '~ ')

Inoltre, ricorda che anche mongo non consente alle chiavi di iniziare con "$", quindi devi fare qualcosa di simile lì

Ecco un codice che lo fa:

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

Questa fuga può ancora interrompersi, se hai stringhe come ". ~ P.". Qui la stringa di escape sarà "~ p ~~ p ~ p". Unescaping ti darà '. ~ ..', che è diverso dalla stringa effettiva.
jvc

1
@jvc Hai ragione! Ho corretto la spiegazione e le funzioni di escape di esempio. Fammi sapere se sono ancora rotte!
BT

3

Una risposta tardiva, ma se usi Spring e Mongo, Spring può gestire la conversione per te con MappingMongoConverter. È la soluzione di JohnnyHK ma gestita da Spring.

@Autowired
private MappingMongoConverter converter;

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

Se il tuo Json memorizzato è:

{ "axxxb" : "value" }

Fino a Spring (MongoClient) verrà letto come:

{ "a.b" : "value" }

richiesto un bean di tipo "org.springframework.data.mongodb.core.convert.MappingMongoConverter" che non è stato trovato.
Sathya Narayan C

1

Uso il seguente codice di escape in JavaScript per ogni chiave oggetto:

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

Quello che mi piace è che si sostituisce solo $all'inizio e non utilizza caratteri Unicode che possono essere difficili da usare nella console. _è per me molto più leggibile di un carattere Unicode. Inoltre, non sostituisce un set di caratteri speciali ( $, .) con un altro (unicode). Ma correttamente fugge con il tradizionale\ .


3
E se qualcuno usa un _ in una qualsiasi delle sue chiavi, riceverai dei bug.
BT

1

Non perfetto, ma funzionerà nella maggior parte delle situazioni: sostituisci i caratteri proibiti con qualcos'altro. Dato che è nelle chiavi, questi nuovi caratteri dovrebbero essere piuttosto rari.

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

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

e i risultati - nota che i valori non vengono modificati:

{
  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

C'è qualche brutto modo per interrogarlo non è consigliabile usarlo nell'applicazione piuttosto che per scopi di debug (funziona solo su oggetti incorporati):

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

Come ha detto un altro utente, codificare / decodificare questo può diventare problematico in futuro, quindi probabilmente è solo più facile sostituire tutti i tasti che hanno un punto. Ecco una funzione ricorsiva che ho creato per sostituire le chiavi con "." occorrenze:

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)

Puoi modificare questo codice per sostituire anche "$", poiché è un altro carattere che mongo non consentirà in una chiave.


0

Per PHP sostituisco il valore HTML per il periodo. Ecco ".".

Memorizza in MongoDB in questo modo:

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

e il codice PHP ...

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

0

Le coppie Lodash ti permetteranno di cambiare

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

in

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

utilizzando

var newObj = _.pairs(oldObj);

0

Puoi memorizzarlo così com'è e convertirlo in pretty after

Ho scritto questo esempio su Livescript. È possibile utilizzare il sito Web livescript.net per valutarlo

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

Produrrà

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


0

Ti do il mio suggerimento: puoi usare JSON.stringify per salvare Object / Array contiene il nome della chiave ha punti, quindi analizzare la stringa in Object con JSON.parse per elaborare quando ottieni dati dal database

Un'altra soluzione alternativa: Ristruttura il tuo schema come:

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

0

L'ultimo MongoDB supporta le chiavi con un punto, ma il driver java MongoDB non supporta. Quindi, per farlo funzionare in Java, ho estratto il codice dal repository github di java-mongo-driver e ho apportato le modifiche di conseguenza nella loro funzione isValid Key, ne ho creato un nuovo jar, usandolo ora.


0

Sostituisci il punto ( .) o il dollaro ( $) con altri caratteri che non verranno mai utilizzati nel documento reale. E ripristina il punto ( .) o il dollaro ( $) durante il recupero del documento. La strategia non influenzerà i dati letti dall'utente.

Puoi selezionare il personaggio da tutti i personaggi .


0

Lo strano è che, usando mongojs, posso creare un documento con un punto se imposto _id da solo, tuttavia non posso creare un documento quando viene generato _id:

Funziona:

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

Non funziona:

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

Inizialmente ho pensato che anche l'aggiornamento di un documento con una chiave punto funzionasse, ma identifica il punto come una sottochiave!

Vedendo come mongojs gestisce il punto (sottochiave), mi assicurerò che le mie chiavi non contengano un punto.


0

Come quello che ha menzionato @JohnnyHK , rimuovi la punteggiatura o "." dalle tue chiavi perché creerà problemi molto più grandi quando i tuoi dati inizieranno ad accumularsi in un set di dati più grande. Ciò causerà problemi soprattutto quando chiami operatori aggregati come $ merge che richiedono l'accesso e il confronto delle chiavi che genereranno un errore. L'ho imparato a mie spese, per favore non ripetere per coloro che stanno iniziando.


-2

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

Trovato nei messaggi di errore. Se usi anaconda(trova il file corrispondente in caso contrario), cambia semplicemente il valore da check_keys = Truea Falsenel file sopra indicato. Funzionerà!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.