web-dev-qa-db-fra.com

Point MongoDB (.) Dans le nom de la clé

Il semble que mongo n'autorise pas l'insertion de clés avec un point (.) Ou un signe dollar ($). Cependant, lorsque j'ai importé un fichier JSON contenant un point à l'aide de l'outil mongoimport, il a bien fonctionné. Le pilote se plaint d'essayer 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 de travers et je ne devrais 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.

52
Michael Yagudaev

MongoDB ne prend pas en charge les touches avec un point . Il vous faudra donc prétraiter votre fichier JSON pour le supprimer/le remplacer avant de l'importer, ou vous allez vous préparer à toutes sortes de problèmes.

Il n’existe pas de solution standard à ce problème, la meilleure approche dépend trop des spécificités de la situation. Mais, dans la mesure du possible, j'éviterais toute approche de codeur/décodeur de clé car vous continuerez à payer l'inconvénient de cette approche à perpétuité, dans laquelle une restructuration JSON représenterait probablement un coût ponctuel.

52
JohnnyHK

Les Mongo docs suggèrent de remplacer les caractères non autorisés tels que $ et . par leurs équivalents unicode.

Dans ces situations, les clés devront remplacer les $ et réservés réservés. personnages. N'importe quel caractère suffit, mais envisagez d'utiliser les équivalents pleine largeur Unicode: U + FF04 (c'est-à-dire «») et U + FF0E (c'est-à-dire «.»).

18
Martin Konecny

Comme mentionné dans d'autres réponses, MongoDB n'autorise pas les caractères $ ou . en tant que clés de carte en raison de restrictions sur les noms de champs . Toutefois, comme indiqué dans Échappement de l'opérateur de signe dollar, cette restriction ne vous empêche pas de insérer des documents avec de telles clés, elle vous empêche simplement de les mettre à jour ou de les interroger.

Le simple problème de remplacer . par [dot] (comme indiqué dans les commentaires) est le suivant: que se passe-t-il lorsque l'utilisateur souhaite légitimement stocker la clé [dot]?

Une approche utilisée par FantomafMorphia consiste à utiliser des séquences d'échappement unicode similaires à celles de Java, mais en veillant à ce que tous les caractères d'échappement soient échappés en premier. En substance, les remplacements de chaînes suivants sont effectués (*):

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

Un remplacement inverse est effectué lorsque les clés de la carte sont ensuite lues à partir de MongoDB.

Ou dans Fantom code:

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 informé de telles conversions est lors de la création de requêtes pour de telles clés.

Étant donné qu’il est courant de stocker dotted.property.names dans des bases de données à des fins de configuration, j’estime que cette approche est préférable à la simple interdiction de toutes ces clés de carte.

(*) afMorphia exécute en fait des règles d'échappement unicode complètes/appropriées, comme indiqué dans La syntaxe d'échappement Unicode en Java , mais la séquence de remplacement décrite fonctionne également.

12
Steve Eynon

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 pourrez ensuite accéder 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) ];
}
11
Henry

Une solution que je viens de mettre en œuvre et qui me convient vraiment consiste à fractionner le nom de clé et la valeur 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. Le doc ressemblerait à ceci:

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

Vous pouvez toujours interroger cela assez facilement, simplement en faisant une find sur les champs keyName et keyValue .

Donc au lieu de:

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

qui ne fonctionnerait pas comme prévu, vous exécuteriez:

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

et il retournera le document attendu.

8
Steve

La dernière version stable (v3.6.1) de MongoDB prend désormais en charge les points (.) Dans les noms de clés ou de champs. 

Les noms de champs peuvent maintenant contenir des points (.) Et des dollars (-)

8
h4ck3d

À partir de Documents MongoDB "the '.' le caractère ne doit apparaître nulle part dans le nom de la clé ". Il semble que vous deviez proposer un schéma d'encodage ou vous en passer.

4
maerics

Vous aurez besoin d'échapper aux clés. Comme il semble que la plupart des gens ne sachent pas comment échapper correctement aux chaînes, voici les étapes:

  1. choisissez un caractère d'échappement (mieux vaut choisir un personnage rarement utilisé). Par exemple. '~'
  2. Pour échapper, remplacez d'abord toutes les occurrences du caractère d'échappement par une séquence précédée de votre caractère d'échappement (par exemple, '~' -> '~ t'), puis remplacez le caractère ou la séquence dont vous avez besoin pour échapper par une séquence précédée de votre caractère d'échappement . Par exemple. '.' -> '~ p'
  3. Pour vous libérer, commencez par supprimer la séquence d'échappement de toutes les occurrences de votre deuxième séquence d'échappement (par exemple, '~ p' -> '.'), Puis transformez votre séquence de caractères d'échappement en un seul caractère d'échappement (par exemple, '~ s' -> '~ ')

De plus, rappelez-vous que mongo n'autorise pas non plus les touches à commencer par '$', vous devez donc faire quelque chose de similaire là-bas

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

Une réponse tardive, mais si vous utilisez Spring avec Mongo, il pourra gérer la conversion pour vous. 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 par exemple

{ "axxxb" : "value" }

À travers Spring (MongoClient), il sera lu comme

{ "a.b" : "value" }
2
PomPom

Il est supporté maintenant

MongoDb 3.6 prend en charge à la fois les points et dollar dans les noms de champs. Voir ci-dessous JIRA: https://jira.mongodb.org/browse/Java-281

Mettre à jour votre Mongodb à la version 3.6+ semble être la meilleure solution.

2
Abhidemon

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 remplace uniquement $ au début et qu’il n’utilise pas de caractères unicode, ce qui peut être délicat à utiliser dans la console. _ est pour moi beaucoup plus lisible qu'un caractère unicode. En outre, il ne remplace pas un jeu de caractères spéciaux ($, .) par un autre (unicode). Mais s'échappe bien avec le \ traditionnel.

1
Mitar

Pas parfait, mais fonctionnera dans la plupart des situations: remplacez les caractères interdits par quelque chose d'autre. Comme il est dans les 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": {}
  }
}
1
Nico

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

Il va produire 

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

0
Andrey Stehno

La dernière version de MongoDB prend en charge les clés avec un point, mais le pilote Java MongoDB n'est pas pris en charge. Donc, pour que cela fonctionne en Java, j'ai extrait le code de github repo de Java-mongo-driver et apporté des modifications en conséquence à la fonction isValid Key, créé un nouveau fichier jar, utilisé maintenant.

0
ANDY MURAY

Ce qui est étrange, c’est que, avec mongojs, je peux créer un document avec un point si je règle moi-même le _id, mais je ne peux pas créer de document lorsque le _id est généré:

Ça marche:

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 touche de point fonctionnait également, mais son identification en tant que sous-clé!

Voyant comment mongojs gère le point (sous-clé), je vais m'assurer que mes clés ne contiennent pas de point.

0
Sam

Lodash pairs vous permettra de changer

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

dans

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

en utilisant 

var newObj = _.pairs(oldObj);
0
steampowered

Pour PHP, je substitue la valeur HTML à 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);      
0
JRForbes

Je vous donne mon conseil: vous pouvez utiliser JSON.stringify pour enregistrer un objet/un tableau contenant le nom de la clé comportant des points, puis analyser la chaîne en objet avec JSON.parse pour le traiter lors de l'extraction des données de la base de données.

Une autre solution de contournement: Restructurez votre schéma de la manière suivante:

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

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 caractère parmi tous les caractères .

0
Simin Jie