Parece que mongo no permite la inserción de claves con un punto (.) O un signo de dólar ($); sin embargo, cuando importé un archivo JSON que contenía un punto usando la herramienta mongoimport, funcionó bien. El conductor se queja de intentar insertar ese elemento.
Así es como se ve el documento en la base de datos:
{
"_id": {
"$oid": "..."
},
"make": "saab",
"models": {
"9.7x": [
2007,
2008,
2009,
2010
]
}
}
¿Estoy haciendo todo esto mal y no debería usar mapas hash como ese con datos externos (es decir, los modelos) o puedo escapar del punto de alguna manera? Tal vez estoy pensando demasiado en Javascript.
javascript
mongodb
nosql
Michael Yagudaev
fuente
fuente
Respuestas:
MongoDB no admite claves con un punto , por lo que tendrá que preprocesar su archivo JSON para eliminarlo / reemplazarlo antes de importarlo o se estará preparando para todo tipo de problemas.
No existe una solución alternativa estándar para este problema, el mejor enfoque depende demasiado de las características específicas de la situación. Pero evitaría cualquier enfoque de codificador / decodificador de clave si es posible, ya que continuará pagando el inconveniente de eso a perpetuidad, donde una reestructuración de JSON presumiblemente sería un costo único.
fuente
models
aquí), y (c) no necesitamos consultarlos por nombre de clave en Mongo. Entonces, un patrón en el que me decidí es enJSON.stringify
este campo al guardar y 'JSON.parse` al recuperar.Como se menciona en otras respuestas, MongoDB no permite
$
o.
caracteres como claves de mapa debido a restricciones en los nombres de campo . Sin embargo, como se menciona en Operador de signo de dólar, escapar de esta restricción no le impide insertar documentos con dichas claves, solo le impide actualizarlos o consultarlos.El problema de simplemente reemplazar
.
con[dot]
oU+FF0E
(como se mencionó en otra parte de esta página) es, ¿qué sucede cuando el usuario legítimamente quiere almacenar la clave[dot]
oU+FF0E
?Un enfoque que adopta el controlador afMorphia de Fantom es usar secuencias de escape Unicode similares a las de Java, pero asegurándose de que el carácter de escape se escape primero. En esencia, se realizan los siguientes reemplazos de cadenas (*):
Se realiza un reemplazo inverso cuando las claves del mapa se leen posteriormente desde MongoDB.
O en código Fantom :
Str encodeKey(Str key) { return key.replace("\\", "\\\\").replace("\$", "\\u0024").replace(".", "\\u002e") } Str decodeKey(Str key) { return key.replace("\\u002e", ".").replace("\\u0024", "\$").replace("\\\\", "\\") }
El único momento en que un usuario debe estar al tanto de tales conversiones es cuando construye consultas para tales claves.
Dado que es común almacenar
dotted.property.names
en bases de datos con fines de configuración, creo que este enfoque es preferible a simplemente prohibir todas estas claves de mapa.(*) afMorphia en realidad realiza reglas de escape Unicode completas / adecuadas como se menciona en la sintaxis de escape Unicode en Java, pero la secuencia de reemplazo descrita funciona igual de bien.
fuente
//g
para reemplazar todas las ocurrencias y no solo la primera. Además, usar los equivalentes de ancho completo como en la respuesta de Martin Konecny parece ser una buena idea. Finalmente, una barra invertida es suficiente para la codificación.key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
U+FF04
.Los documentos de Mongo sugieren reemplazar caracteres ilegales como
$
y.
por sus equivalentes Unicode.fuente
db.test.insert({"field\uff0ename": "test"})
La última versión estable (v3.6.1) de MongoDB ahora admite puntos (.) En las claves o nombres de campo.
Los nombres de campo ahora pueden contener puntos (.) Y caracteres de dólar ($)
fuente
mongoClient.getDatabase("mydb").getCollection("test").insertOne(new Document("value", new Document("key.with.dots", "value").append("$dollar", "value")));
falla al usar mongodb-driver.3.6.3 y MongoDB 3.6.3.mongodb-4.1.1
ypymongo-3.7.1
. Puedo agregar documentos que contengan claves con.
robomongo pero no depymongo
, el umbral aumentaInvalidDocument: key '1.1' must not contain '.'
Desearía que se hubiera solucionado a estas alturas ...Una solución que acabo de implementar y con la que estoy muy contento consiste en dividir el nombre y el valor de la clave en dos campos separados. De esta manera, puedo mantener los personajes exactamente iguales y no preocuparme por ninguna de esas pesadillas de análisis. El documento se vería así:
{ ... keyName: "domain.com", keyValue: "unregistered", ... }
Aún puede consultar esto con bastante facilidad, simplemente haciendo un
find
en los campos keyName y keyValue .Entonces en lugar de:
db.collection.find({"domain.com":"unregistered"})
que en realidad no funcionaría como se esperaba, ejecutaría:
db.collection.find({keyName:"domain.com", keyValue:"unregistered"})
y devolverá el documento esperado.
fuente
Puede intentar usar un hash en la clave en lugar del valor y luego almacenar ese valor en el valor 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 ] }
Luego accedería a los modelos usando el hash más tarde.
var version = "9.7x"; collection.find( { _id : ...}, function(e, data ) { var models = data.models[ md5(version) ]; }
fuente
Es compatible ahora
MongoDb 3.6 en adelante admite puntos y dólares en los nombres de campo. Vea a continuación JIRA: https://jira.mongodb.org/browse/JAVA-2810
Actualizar su Mongodb a 3.6+ parece ser la mejor manera de hacerlo.
fuente
De los documentos de MongoDB, "el '.' el carácter no debe aparecer en ninguna parte del nombre de la clave ". Parece que tendrás que idear un esquema de codificación o prescindir de él.
fuente
Necesitarás escapar de las llaves. Dado que parece que la mayoría de la gente no sabe cómo escapar correctamente de las cuerdas, estos son los pasos:
Además, recuerde que mongo tampoco permite que las teclas comiencen con '$', por lo que debe hacer algo similar allí
Aquí hay un código que lo hace:
// 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, '~') }
fuente
Una respuesta tardía, pero si usa Spring y Mongo, Spring puede administrar la conversión por usted con
MappingMongoConverter
. Es la solución de JohnnyHK pero manejada por Spring.@Autowired private MappingMongoConverter converter; @PostConstruct public void configureMongo() { converter.setMapKeyDotReplacement("xxx"); }
Si su Json almacenado es:
{ "axxxb" : "value" }
A través de Spring (MongoClient) se leerá como:
{ "a.b" : "value" }
fuente
Utilizo el siguiente escape en JavaScript para cada clave de objeto:
key.replace(/\\/g, '\\\\').replace(/^\$/, '\\$').replace(/\./g, '\\_')
Lo que me gusta de él es que reemplaza solo
$
al principio y no usa caracteres Unicode que pueden ser difíciles de usar en la consola._
es para mí mucho más legible que un carácter Unicode. Tampoco reemplaza un conjunto de caracteres especiales ($
,.
) con otro (unicode). Pero se escapa propiamente con lo tradicional\
.fuente
No es perfecto, pero funcionará en la mayoría de situaciones: reemplace los caracteres prohibidos por otra cosa. Dado que está en las claves, estos nuevos caracteres deberían ser bastante raros.
/** 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 }
Aquí hay una prueba:
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) )
y los resultados - tenga en cuenta que los valores no se modifican:
{ 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": {} } }
fuente
Hay una forma desagradable de consultar, no se recomienda usarla en la aplicación en lugar de con fines de depuración (funciona solo en objetos incrustados):
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 ])
fuente
Como mencionó otro usuario, codificar / decodificar esto puede volverse problemático en el futuro, por lo que probablemente sea más fácil reemplazar todas las claves que tienen un punto. Aquí hay una función recursiva que hice para reemplazar las teclas con '.' ocurrencias:
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)
También puede modificar este código para reemplazar '$', ya que ese es otro carácter que mongo no permitirá en una clave.
fuente
Para PHP, sustituyo el valor HTML por el período. Eso es
"."
.Se almacena en MongoDB así:
"validations" : { "4e25adbb1b0a55400e030000" : { "associate" : "true" }, "4e25adb11b0a55400e010000" : { "associate" : "true" } }
y el código PHP ...
$entry = array('associate' => $associate); $data = array( '$set' => array( 'validations.' . str_replace(".", `"."`, $validation) => $entry )); $newstatus = $collection->update($key, $data, $options);
fuente
Los pares de Lodash te permitirán cambiar
{ 'connect.sid': 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' }
dentro
[ [ 'connect.sid', 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' ] ]
utilizando
var newObj = _.pairs(oldObj);
fuente
Puede almacenarlo como está y convertirlo en bonito después
Escribí este ejemplo en Livescript. Puede usar el sitio web livescript.net para evaluarlo
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
Producirá
{"field.field1":1, "field.field2":2, "field.field3":5, "field.nested.more":1, "field.nested.moresdafasdf":23423, "field3":3}
fuente
Darle mi consejo: puede usar JSON.stringify para guardar Object / Array contiene el nombre de la clave tiene puntos, luego analizar la cadena a Object con JSON.parse para procesar cuando obtenga datos de la base de datos
Otra solución alternativa: reestructura tu esquema como:
key : { "keyName": "a.b" "value": [Array] }
fuente
El último MongoDB admite claves con un punto, pero el controlador java MongoDB no es compatible. Entonces, para que funcione en Java, extraje el código del repositorio de github de java-mongo-driver e hice los cambios correspondientes en su función de clave isValid, creé un nuevo jar y lo usé ahora.
fuente
Reemplace el punto (
.
) o el dólar ($
) con otros caracteres que nunca se usarán en el documento real. Y restaure el punto (.
) o el dólar ($
) al recuperar el documento. La estrategia no influirá en los datos que lea el usuario.Puede seleccionar el personaje de todos los personajes .
fuente
Lo extraño es que, usando mongojs, puedo crear un documento con un punto si configuro el _id yo mismo, sin embargo, no puedo crear un documento cuando se genera el _id:
Funciona:
db.testcollection.save({"_id": "testdocument", "dot.ted.": "value"}, (err, res) => { console.log(err, res); });
No funciona:
db.testcollection.save({"dot.ted": "value"}, (err, res) => { console.log(err, res); });
Primero pensé que actualizar un documento con una clave de punto también funcionaba, ¡pero es identificar el punto como una subclave!
Al ver cómo mongojs maneja el punto (subclave), me aseguraré de que mis claves no contengan un punto.
fuente
Como lo que ha mencionado @JohnnyHK , elimine los signos de puntuación o '.' de sus claves porque creará problemas mucho mayores cuando sus datos comiencen a acumularse en un conjunto de datos más grande. Esto causará problemas, especialmente cuando llame a operadores agregados como $ merge, que requiere acceder y comparar claves que arrojarán un error. Lo he aprendido de la manera difícil, por favor no repita para los que están comenzando.
fuente
/home/user/anaconda3/lib/python3.6/site-packages/pymongo/collection.py
Lo encontré en mensajes de error. Si usa
anaconda
(busque el archivo correspondiente si no es así), simplemente cambie el valor decheck_keys = True
aFalse
en el archivo indicado arriba. ¡Eso funcionará!fuente