Il semble que mongo n'autorise pas l'insertion de clés avec un point (.) Ou un signe dollar ($), mais lorsque j'ai importé un fichier JSON contenant un point à l'aide de l'outil mongoimport, cela fonctionnait bien. Le conducteur se plaint d'avoir tenté d'insérer cet élément.
Voici à quoi ressemble le document dans la base de données:
{
"_id": {
"$oid": "..."
},
"make": "saab",
"models": {
"9.7x": [
2007,
2008,
2009,
2010
]
}
}
Est-ce que je fais tout cela mal et ne devrais-je pas utiliser des cartes de hachage comme celles-ci avec des données externes (c'est-à-dire les modèles) ou puis-je échapper au point d'une manière ou d'une autre? Peut-être que je pense trop à Javascript.
javascript
mongodb
nosql
Michael Yagudaev
la source
la source
Réponses:
MongoDB ne prend pas en charge les clés avec un point , vous devrez donc prétraiter votre fichier JSON pour les supprimer / les remplacer avant de l'importer ou vous vous préparerez à toutes sortes de problèmes.
Il n'y a pas de solution de contournement standard à ce problème, la meilleure approche dépend trop des spécificités de la situation. Mais j'éviterais toute approche d'encodeur / décodeur de clé si possible, car vous continuerez à en payer les inconvénients à perpétuité, où une restructuration JSON serait vraisemblablement un coût unique.
la source
models
ici), et (c) nous n'avons pas besoin de les interroger par nom de clé en Mongo. Donc, un modèle sur lequel je me suis installé est celui deJSON.stringify
ce champ lors de la sauvegarde et de 'JSON.parse` lors de la récupération.Comme mentionné dans d'autres réponses, MongoDB n'autorise pas les caractères
$
ou.
comme clés de carte en raison des restrictions sur les noms de champ . Cependant, comme mentionné dans Dollar Sign Operator Échapper à cette restriction ne vous empêche pas d' insérer des documents avec de telles clés, cela vous empêche simplement de les mettre à jour ou de les interroger.Le problème du simple remplacement
.
par[dot]
ouU+FF0E
(comme mentionné ailleurs sur cette page) est le suivant: que se passe-t-il lorsque l'utilisateur souhaite légitimement stocker la clé[dot]
ouU+FF0E
?Une approche adoptée par le pilote afMorphia de Fantom consiste à utiliser des séquences d'échappement unicode similaires à celles de Java, mais en veillant à ce que le caractère d'échappement soit d'abord échappé. En substance, les remplacements de chaînes suivants sont effectués (*):
Un remplacement inversé est effectué lorsque les clés de la carte sont ensuite lues à partir de MongoDB.
Ou en code Fantom :
Str encodeKey(Str key) { return key.replace("\\", "\\\\").replace("\$", "\\u0024").replace(".", "\\u002e") } Str decodeKey(Str key) { return key.replace("\\u002e", ".").replace("\\u0024", "\$").replace("\\\\", "\\") }
Le seul moment où un utilisateur doit être conscient de ces conversions est lors de la construction de requêtes pour ces clés.
Étant donné qu'il est courant de stocker
dotted.property.names
dans des bases de données à des fins de configuration, je pense que cette approche est préférable à l'interdiction simplement de toutes ces clés de carte.(*) afMorphia exécute en fait des règles d'échappement Unicode complètes / appropriées comme mentionné dans la syntaxe d'échappement Unicode en Java, mais la séquence de remplacement décrite fonctionne tout aussi bien.
la source
//g
pour remplacer toutes les occurrences et pas seulement la première. De plus, utiliser les équivalents pleine largeur comme dans la réponse de Martin Konecny semble être une bonne idée. Enfin, une barre oblique inverse suffit pour l'encodage.key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
U+FF04
.La documentation Mongo suggère de remplacer les caractères illégaux tels que
$
et.
par leurs équivalents Unicode.la source
db.test.insert({"field\uff0ename": "test"})
La dernière version stable (v3.6.1) de MongoDB prend désormais en charge les points (.) Dans les clés ou les noms de champ.
Les noms de champ peuvent désormais contenir des points (.) Et des caractères dollar ($)
la source
mongoClient.getDatabase("mydb").getCollection("test").insertOne(new Document("value", new Document("key.with.dots", "value").append("$dollar", "value")));
Cela échoue en utilisant mongodb-driver.3.6.3 et MongoDB 3.6.3.mongodb-4.1.1
etpymongo-3.7.1
. Je peux ajouter des documents contenant des clés avec avec.
avec robomongo mais pas à partir depymongo
, cela soulève encore leInvalidDocument: key '1.1' must not contain '.'
souhait qu'il ait été corrigé maintenant ...Une solution que je viens de mettre en œuvre et qui me satisfait vraiment consiste à diviser le nom et la valeur de la clé en deux champs distincts. De cette façon, je peux garder les personnages exactement les mêmes et ne pas m'inquiéter de ces cauchemars d'analyse. Le document ressemblerait à:
{ ... keyName: "domain.com", keyValue: "unregistered", ... }
Vous pouvez toujours interroger cela assez facilement, simplement en effectuant un
find
sur les champs keyName et keyValue .Donc au lieu de:
db.collection.find({"domain.com":"unregistered"})
qui ne fonctionnerait pas réellement comme prévu, vous exécuteriez:
db.collection.find({keyName:"domain.com", keyValue:"unregistered"})
et il renverra le document attendu.
la source
Vous pouvez essayer d'utiliser un hachage dans la clé au lieu de la valeur, puis stocker cette valeur dans la valeur 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 ] }
Vous accéderiez ensuite aux modèles en utilisant le hachage plus tard.
var version = "9.7x"; collection.find( { _id : ...}, function(e, data ) { var models = data.models[ md5(version) ]; }
la source
Il est pris en charge maintenant
MongoDb 3.6 et les versions ultérieures prennent en charge les points et le dollar dans les noms de champ. Voir ci-dessous JIRA: https://jira.mongodb.org/browse/JAVA-2810
La mise à niveau de votre Mongodb vers 3.6+ semble être la meilleure solution.
la source
À partir de la documentation MongoDB, "le '.' le caractère ne doit apparaître nulle part dans le nom de la clé ". Il semble que vous deviez créer un schéma d'encodage ou vous en passer.
la source
Vous devrez échapper aux clés. Comme il semble que la plupart des gens ne savent pas comment échapper correctement aux chaînes, voici les étapes:
Souvenez-vous également que mongo n'autorise pas non plus les clés à commencer par '$', vous devez donc faire quelque chose de similaire ici
Voici un code qui le fait:
// 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, '~') }
la source
Une réponse tardive, mais si vous utilisez Spring et Mongo, Spring peut gérer la conversion pour vous avec
MappingMongoConverter
. C'est la solution de JohnnyHK mais gérée par Spring.@Autowired private MappingMongoConverter converter; @PostConstruct public void configureMongo() { converter.setMapKeyDotReplacement("xxx"); }
Si votre Json stocké est:
{ "axxxb" : "value" }
Grâce à Spring (MongoClient), il sera lu comme suit:
{ "a.b" : "value" }
la source
J'utilise l'échappement suivant en JavaScript pour chaque clé d'objet:
key.replace(/\\/g, '\\\\').replace(/^\$/, '\\$').replace(/\./g, '\\_')
Ce que j'aime, c'est qu'il ne remplace qu'au
$
début, et qu'il n'utilise pas de caractères unicode qui peuvent être difficiles à utiliser dans la console._
est pour moi beaucoup plus lisible qu'un caractère unicode. Il ne remplace pas non plus un jeu de caractères spéciaux ($
,.
) par un autre (unicode). Mais s'échappe correctement avec le traditionnel\
.la source
Pas parfait, mais fonctionnera dans la plupart des situations: remplacez les caractères interdits par autre chose. Comme il s'agit de clés, ces nouveaux caractères devraient être assez rares.
/** 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 }
Voici 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) )
et les résultats - notez que les valeurs ne sont pas modifiées:
{ 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": {} } }
la source
Il existe une manière moche d'interroger il n'est pas recommandé de l'utiliser dans l'application plutôt qu'à des fins de débogage (ne fonctionne que sur les objets intégrés):
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 ])
la source
Comme un autre utilisateur l'a mentionné, l'encodage / décodage peut devenir problématique à l'avenir, il est donc probablement plus facile de remplacer toutes les clés qui ont un point. Voici une fonction récursive que j'ai créée pour remplacer les clés par '.' occurrences:
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)
Vous pouvez également modifier ce code pour remplacer '$', car c'est un autre caractère que mongo n'autorisera pas dans une clé.
la source
Pour PHP, je remplace la valeur HTML par la période. C'est
"."
.Il stocke dans MongoDB comme ceci:
"validations" : { "4e25adbb1b0a55400e030000" : { "associate" : "true" }, "4e25adb11b0a55400e010000" : { "associate" : "true" } }
et le code PHP ...
$entry = array('associate' => $associate); $data = array( '$set' => array( 'validations.' . str_replace(".", `"."`, $validation) => $entry )); $newstatus = $collection->update($key, $data, $options);
la source
Les paires Lodash vous permettront de changer
{ 'connect.sid': 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' }
dans
[ [ 'connect.sid', 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' ] ]
en utilisant
var newObj = _.pairs(oldObj);
la source
Vous pouvez le stocker tel quel et le convertir en joli après
J'ai écrit cet exemple sur Livescript. Vous pouvez utiliser le site Web livescript.net pour l'évaluer
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
Cela produira
{"field.field1":1, "field.field2":2, "field.field3":5, "field.nested.more":1, "field.nested.moresdafasdf":23423, "field3":3}
la source
Donnez-vous mon conseil: vous pouvez utiliser JSON.stringify pour enregistrer Object / Array contient le nom de la clé a des points, puis analyser la chaîne en Object avec JSON.parse à traiter lors de l'obtention des données de la base de données
Une autre solution de contournement: Restructurez votre schéma comme:
key : { "keyName": "a.b" "value": [Array] }
la source
Le dernier MongoDB prend en charge les clés avec un point, mais le pilote Java MongoDB ne prend pas en charge. Donc, pour le faire fonctionner en Java, j'ai extrait le code du repo github de java-mongo-driver et apporté des modifications en conséquence dans leur fonction isValid Key , en ai créé un nouveau fichier, en l'utilisant maintenant.
la source
Remplacez le point (
.
) ou le dollar ($
) par d'autres caractères qui ne seront jamais utilisés dans le document réel. Et restaurez le point (.
) ou le dollar ($
) lors de la récupération du document. La stratégie n'influencera pas les données lues par l'utilisateur.Vous pouvez sélectionner le personnage parmi tous les personnages .
la source
Ce qui est étrange, c'est qu'en utilisant mongojs, je peux créer un document avec un point si je définis le _id moi-même, mais je ne peux pas créer de document lorsque le _id est généré:
Fonctionne:
db.testcollection.save({"_id": "testdocument", "dot.ted.": "value"}, (err, res) => { console.log(err, res); });
Ne marche pas:
db.testcollection.save({"dot.ted": "value"}, (err, res) => { console.log(err, res); });
J'ai d'abord pensé que la mise à jour d'un document avec une clé de point fonctionnait également, mais son identification du point comme sous-clé!
Voyant comment mongojs gère le point (sous-clé), je vais m'assurer que mes clés ne contiennent pas de point.
la source
Comme ce que @JohnnyHK a mentionné, supprimez les ponctuations ou "." de vos clés car cela créera des problèmes beaucoup plus importants lorsque vos données commenceront à s'accumuler dans un ensemble de données plus grand. Cela posera des problèmes en particulier lorsque vous appelez des opérateurs d'agrégation comme $ merge, qui nécessitent d'accéder et de comparer des clés, ce qui générera une erreur. Je l'ai appris à la dure s'il vous plaît ne répétez pas pour ceux qui débutent.
la source
/home/user/anaconda3/lib/python3.6/site-packages/pymongo/collection.py
Je l'ai trouvé dans les messages d'erreur. Si vous utilisez
anaconda
(trouvez le fichier correspondant sinon), changez simplement la valeur decheck_keys = True
àFalse
dans le fichier indiqué ci-dessus. Cela fonctionnera!la source