Parece que o mongo não permite a inserção de chaves com um ponto (.) Ou cifrão ($), mas quando importei um arquivo JSON que continha um ponto usando a ferramenta mongoimport, funcionou bem. O motorista está reclamando de tentar inserir aquele elemento.
Esta é a aparência do documento no banco de dados:
{
"_id": {
"$oid": "..."
},
"make": "saab",
"models": {
"9.7x": [
2007,
2008,
2009,
2010
]
}
}
Estou fazendo tudo errado e não deveria usar mapas hash como esse com dados externos (ou seja, os modelos) ou posso escapar do ponto de alguma forma? Talvez eu esteja pensando demais no estilo Javascript.
javascript
mongodb
nosql
Michael Yagudaev
fonte
fonte
Respostas:
O MongoDB não oferece suporte a chaves com um ponto , então você terá que pré-processar seu arquivo JSON para removê-los / substituí-los antes de importá-lo ou estará se preparando para todos os tipos de problemas.
Não há uma solução alternativa padrão para esse problema, a melhor abordagem depende muito das especificidades da situação. Mas eu evitaria qualquer abordagem de codificador / decodificador de chave, se possível, já que você continuará a pagar pela inconveniência disso perpetuamente, onde uma reestruturação JSON seria presumivelmente um custo único.
fonte
models
aqui) e (c) não precisamos consultá-los por nome de chave no Mongo. Portanto, um padrão que estabeleci éJSON.stringify
este campo ao salvar e 'JSON.parse` ao recuperar.Conforme mencionado em outras respostas, o MongoDB não permite caracteres
$
ou.
como chaves de mapa devido a restrições nos nomes de campo . No entanto, conforme mencionado em Dollar Sign Operator Escaping esta restrição não o impede de inserir documentos com tais chaves, apenas impede que você os atualize ou consulte.O problema de simplesmente substituir
.
por[dot]
ouU+FF0E
(conforme mencionado em outra parte desta página) é, o que acontece quando o usuário deseja legitimamente armazenar a chave[dot]
ouU+FF0E
?Uma abordagem que o driver afMorphia do Fantom adota é usar sequências de escape Unicode semelhantes às do Java, mas garantindo que o caractere de escape seja escapado primeiro. Em essência, as seguintes substituições de string são feitas (*):
Uma substituição reversa é feita quando as chaves do mapa são lidas subsequentemente no MongoDB.
Ou no 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("\\\\", "\\") }
O único momento em que o usuário precisa estar ciente dessas conversões é ao construir consultas para essas chaves.
Visto que é comum armazenar
dotted.property.names
em bancos de dados para fins de configuração, acredito que essa abordagem seja preferível a simplesmente banir todas essas chaves de mapa.(*) afMorphia realmente executa regras de escape unicode completas / adequadas, conforme mencionado na sintaxe de escape Unicode em Java, mas a sequência de substituição descrita funciona da mesma forma.
fonte
//g
para substituir todas as ocorrências e não apenas a primeira. Além disso, usar os equivalentes de largura total como na resposta de Martin Konecny parece ser uma boa ideia. Finalmente, uma barra invertida é suficiente para a codificação.key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
U+FF04
.Os documentos do Mongo sugerem a substituição de caracteres ilegais, como
$
e.
por seus equivalentes Unicode.fonte
db.test.insert({"field\uff0ename": "test"})
A última versão estável (v3.6.1) do MongoDB agora oferece suporte a pontos (.) Nas chaves ou nomes de campo.
Os nomes dos campos podem conter caracteres de pontos (.) E cifrões ($) agora
fonte
mongoClient.getDatabase("mydb").getCollection("test").insertOne(new Document("value", new Document("key.with.dots", "value").append("$dollar", "value")));
Falha ao usar mongodb-driver.3.6.3 e MongoDB 3.6.3.mongodb-4.1.1
epymongo-3.7.1
. Posso adicionar documentos contendo chaves com o.
robomongo, mas não dopymongo
, ele ainda levanta oInvalidDocument: key '1.1' must not contain '.'
desejo de que já tivesse sido consertado ...Uma solução que acabei de implementar e com a qual estou muito feliz envolve a divisão do nome e valor da chave em dois campos separados. Assim, posso manter os caracteres exatamente iguais e não me preocupar com nenhum desses pesadelos de análise. O documento seria assim:
{ ... keyName: "domain.com", keyValue: "unregistered", ... }
Você ainda pode consultar isso facilmente, apenas fazendo um
find
nos campos keyName e keyValue .Então, em vez de:
db.collection.find({"domain.com":"unregistered"})
que não funcionasse como esperado, você executaria:
db.collection.find({keyName:"domain.com", keyValue:"unregistered"})
e ele retornará o documento esperado.
fonte
Você pode tentar usar um hash na chave em vez do valor e, em seguida, armazenar esse valor no 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 ] }
Você acessaria os modelos usando o hash posteriormente.
var version = "9.7x"; collection.find( { _id : ...}, function(e, data ) { var models = data.models[ md5(version) ]; }
fonte
É suportado agora
MongoDb 3.6 em diante suporta pontos e cifrões em nomes de campo. Veja abaixo JIRA: https://jira.mongodb.org/browse/JAVA-2810
Atualizar seu Mongodb para 3.6+ parece ser a melhor maneira de fazer.
fonte
Dos documentos do MongoDB "o '.' caractere não deve aparecer em nenhum lugar no nome da chave ". Parece que você terá que criar um esquema de codificação ou ficar sem ele.
fonte
Você precisará escapar das chaves. Uma vez que parece que a maioria das pessoas não sabe como escapar adequadamente das strings, aqui estão as etapas:
Além disso, lembre-se de que o mongo também não permite que as teclas comecem com '$', então você deve fazer algo semelhante aqui
Aqui está um código que faz isso:
// 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, '~') }
fonte
Uma resposta tardia, mas se você usar Spring e Mongo, Spring pode gerenciar a conversão para você com
MappingMongoConverter
. É a solução da JohnnyHK, mas tratada pela Spring.@Autowired private MappingMongoConverter converter; @PostConstruct public void configureMongo() { converter.setMapKeyDotReplacement("xxx"); }
Se o Json armazenado for:
{ "axxxb" : "value" }
Por meio do Spring (MongoClient), ele será lido como:
{ "a.b" : "value" }
fonte
Eu uso o seguinte escape em JavaScript para cada chave de objeto:
key.replace(/\\/g, '\\\\').replace(/^\$/, '\\$').replace(/\./g, '\\_')
O que eu gosto nele é que ele substitui apenas
$
no início e não usa caracteres Unicode, que podem ser difíceis de usar no console._
é para mim muito mais legível do que um caractere Unicode. Também não substitui um conjunto de caracteres especiais ($
,.
) por outro (Unicode). Mas escapa corretamente com o tradicional\
.fonte
Não é perfeito, mas funcionará na maioria das situações: substitua os caracteres proibidos por outra coisa. Como está nas chaves, esses novos caracteres devem 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 }
Aqui está um teste:
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 os resultados - observe que os valores não são modificados:
{ 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": {} } }
fonte
Existe uma maneira feia de consultar, não recomendado para usá-lo no aplicativo em vez de para fins de depuração (funciona apenas em objetos incorporados):
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 ])
fonte
Como outro usuário mencionou, codificar / decodificar isso pode se tornar problemático no futuro, então provavelmente é mais fácil substituir todas as chaves que têm um ponto. Aqui está uma função recursiva que criei para substituir as chaves por '.' ocorrências:
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)
Você pode modificar este código para substituir '$' também, já que é outro caractere que o mongo não permite em uma chave.
fonte
Para PHP, substituo o valor HTML do período. Isso é
"."
.Ele armazena no MongoDB assim:
"validations" : { "4e25adbb1b0a55400e030000" : { "associate" : "true" }, "4e25adb11b0a55400e010000" : { "associate" : "true" } }
e o código PHP ...
$entry = array('associate' => $associate); $data = array( '$set' => array( 'validations.' . str_replace(".", `"."`, $validation) => $entry )); $newstatus = $collection->update($key, $data, $options);
fonte
Os pares Lodash permitirão que você mude
{ 'connect.sid': 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' }
para dentro
[ [ 'connect.sid', 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' ] ]
usando
var newObj = _.pairs(oldObj);
fonte
Você pode armazená-lo como está e converter em bonito depois
Escrevi este exemplo no Livescript. Você pode usar o site livescript.net para avaliá-lo
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
Vai produzir
{"field.field1":1, "field.field2":2, "field.field3":5, "field.nested.more":1, "field.nested.moresdafasdf":23423, "field3":3}
fonte
Dou minha dica: você pode usar JSON.stringify para salvar Object / Array contém o nome da chave com pontos e, em seguida, analisar string para Object com JSON.parse para processar quando obter dados do banco de dados
Outra solução alternativa: reestruture seu esquema como:
key : { "keyName": "a.b" "value": [Array] }
fonte
O MongoDB mais recente oferece suporte a chaves com um ponto, mas o driver Java MongoDB não é compatível. Então, para fazê-lo funcionar em Java, extraí o código do github repo do java-mongo-driver e fiz as alterações correspondentes na função isValid Key, criei um novo jar a partir dele, usando-o agora.
fonte
Substitua o ponto (
.
) ou o dólar ($
) por outros caracteres que nunca serão usados no documento real. E restaure o ponto (.
) ou o dólar ($
) ao recuperar o documento. A estratégia não influenciará os dados que o usuário lê.Você pode selecionar o personagem de todos os personagens .
fonte
O estranho é que, usando mongojs, posso criar um documento com um ponto se definir o _id sozinho, no entanto, não posso criar um documento quando o _id é gerado:
Funciona:
db.testcollection.save({"_id": "testdocument", "dot.ted.": "value"}, (err, res) => { console.log(err, res); });
Não funciona:
db.testcollection.save({"dot.ted": "value"}, (err, res) => { console.log(err, res); });
A princípio pensei que atualizar um documento com uma chave de ponto também funcionaria, mas é identificar o ponto como uma subchave!
Vendo como mongojs lida com o ponto (subchave), vou garantir que minhas chaves não contenham um ponto.
fonte
Como o que @JohnnyHK mencionou, remova pontuações ou '.' de suas chaves porque criará problemas muito maiores quando seus dados começarem a se acumular em um conjunto de dados maior. Isso causará problemas, especialmente quando você chamar operadores agregados como $ merge, que requer o acesso e a comparação de chaves, o que gerará um erro. Eu aprendi da maneira mais difícil, por favor, não repita para aqueles que estão começando.
fonte
/home/user/anaconda3/lib/python3.6/site-packages/pymongo/collection.py
Encontrado em mensagens de erro. Se você usar
anaconda
(encontre o arquivo correspondente, se não), simplesmente altere o valor decheck_keys = True
paraFalse
no arquivo indicado acima. Isso vai funcionar!fonte