¿Cómo actualizo parcialmente un objeto en MongoDB para que el nuevo objeto se superponga / fusione con el existente?

Dado este documento guardado en MongoDB

{ _id : ..., some_key: { param1 : "val1", param2 : "val2", param3 : "val3" } } 

Se debe guardar un objeto con nueva información sobre param2 y param3 del mundo exterior

 var new_info = { param2 : "val2_new", param3 : "val3_new" }; 

Quiero fusionar / superponer los nuevos campos sobre el estado existente del objeto para que param1 no se elimine

Haciendo esto

 db.collection.update( { _id:...} , { $set: { some_key : new_info } } 

Llevará a que MongoDB esté haciendo exactamente lo que se le pidió y establece some_key a ese valor. reemplazando el anterior.

 { _id : ..., some_key: { param2 : "val2_new", param3 : "val3_new" } } 

¿Cuál es la forma de que MongoDB actualice solo campos nuevos (sin indicarlos uno por uno explícitamente)? para obtener esto:

 { _id : ..., some_key: { param1 : "val1", param2 : "val2_new", param3 : "val3_new" } } 

Estoy usando el cliente de Java, pero cualquier ejemplo será apreciado

Si entiendo la pregunta correctamente, quiere actualizar un documento con los contenidos de otro documento, pero solo los campos que aún no están presentes, e ignorar por completo los campos que ya están configurados (incluso a otro valor).

No hay forma de hacerlo en un solo comando.

Primero tiene que consultar el documento, descubrir qué es lo que quiere $set y luego actualizarlo (usando los valores anteriores como un filtro que hace juego para asegurarse de no obtener actualizaciones simultáneas).


Otra lectura de su pregunta sería que está satisfecho con $set , pero no desea establecer explícitamente todos los campos. ¿Cómo pasaría los datos?

Usted sabe que puede hacer lo siguiente:

 db.collection.update( { _id:...} , { $set: someObjectWithNewData } 

Lo resolví con mi propia función. Si desea actualizar el campo especificado en el documento, debe abordarlo con claridad.

Ejemplo:

 { _id : ..., some_key: { param1 : "val1", param2 : "val2", param3 : "val3" } } 

Si solo desea actualizar param2, es incorrecto hacerlo:

 db.collection.update( { _id:...} , { $set: { some_key : new_info } } //WRONG 

Debes usar:

 db.collection.update( { _id:...} , { $set: { some_key.param2 : new_info } } 

Entonces escribí una función algo así:

 function _update($id, $data, $options=array()){ $temp = array(); foreach($data as $key => $value) { $temp["some_key.".$key] = $value; } $collection->update( array('_id' => $id), array('$set' => $temp) ); } _update('1', array('param2' => 'some data')); 

Puede usar la notación de puntos para acceder y establecer campos en el interior de los objetos, sin afectar las otras propiedades de esos objetos.

Dado el objeto que especificó arriba:

 > db.test.insert({"id": "test_object", "some_key": {"param1": "val1", "param2": "val2", "param3": "val3"}}) WriteResult({ "nInserted" : 1 }) 

Podemos actualizar solo some_key.param2 y some_key.param3 :

 > db.test.findAndModify({ ... query: {"id": "test_object"}, ... update: {"$set": {"some_key.param2": "val2_new", "some_key.param3": "val3_new"}}, ... new: true ... }) { "_id" : ObjectId("56476e04e5f19d86ece5b81d"), "id" : "test_object", "some_key" : { "param1" : "val1", "param2" : "val2_new", "param3" : "val3_new" } } 

Puede profundizar tan profundo como desee. Esto también es útil para agregar nuevas propiedades a un objeto sin afectar las existentes.

La mejor solución es extraer propiedades del objeto y convertirlas en pares de valores-clave de notación de puntos planos. Podría usar, por ejemplo, esta biblioteca:

https://www.npmjs.com/package/mongo-dot-notation

Tiene la función .flatten que le permite cambiar el objeto en un conjunto plano de propiedades que podrían .flatten al modificador $ set, sin preocupaciones de que cualquier propiedad de su objeto DB existente sea eliminada / sobrescrita sin necesidad.

Tomado de documentos de mongo-dot-notation :

 var person = { firstName: 'John', lastName: 'Doe', address: { city: 'NY', street: 'Eighth Avenu', number: 123 } }; var instructions = dot.flatten(person) console.log(instructions); /* { $set: { 'firstName': 'John', 'lastName': 'Doe', 'address.city': 'NY', 'address.street': 'Eighth Avenu', 'address.number': 123 } } */ 

Y luego forma el selector perfecto – actualizará SÓLO las propiedades dadas. EDITAR: me gusta ser arqueólogo algunas veces;)

Mongo le permite actualizar documentos nesteds usando a . convención. Eche un vistazo: Actualización de documentos nesteds en mongodb . Aquí hay otra pregunta del pasado sobre una actualización de fusión, como la que estás buscando. Creo que: MongoDB atomic update via ‘merge’ document

Parece que puede establecer isPartialObject que podría lograr lo que desea.

Tuve éxito haciéndolo de esta manera:

 db.collection.update( { _id:...} , { $set: { 'key.another_key' : new_info } } ); 

Tengo una función que maneja dinámicamente las actualizaciones de mi perfil

 function update(prop, newVal) { const str = `profile.${prop}`; db.collection.update( { _id:...}, { $set: { [str]: newVal } } ); } 

Nota: ‘perfil’ es específico para mi implementación, es solo la cadena de la clave que desea modificar.

Sí, la mejor manera es convertir la notación del objeto a una representación de cadena de clave-valor plana, como se menciona en este comentario: https://stackoverflow.com/a/39357531/2529199

Quería resaltar un método alternativo usando esta biblioteca de NPM: https://www.npmjs.com/package/dot-object, que te permite manipular diferentes objetos usando notación de puntos.

Usé este patrón para crear programáticamente una propiedad de objeto nested al aceptar el valor-clave como una variable de función, de la siguiente manera:

 const dot = require('dot-object'); function(docid, varname, varvalue){ let doc = dot.dot({ [varname]: varvalue }); Mongo.update({_id:docid},{$set:doc}); } 

Este patrón me permite usar propiedades anidadas y de un solo nivel indistintamente, e insertarlas limpiamente en Mongo.

Si necesita jugar con JS Objects más allá de Mongo, especialmente en el lado del cliente, pero tiene consistencia al trabajar con Mongo, esta biblioteca le ofrece más opciones que el módulo NPM de mongo-dot-notation mencionado anteriormente.

PD Originalmente quería mencionar esto como un comentario, pero aparentemente mi representante de S / O no es lo suficientemente alto como para publicar un comentario. Por lo tanto, no tratando de concentrarse en el comentario de SzybkiSasza, solo quería resaltar el proporcionar un módulo alternativo.

use $ set haga este proceso

 .update({"_id": args.dashboardId, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}}) .exec() .then(dashboardDoc => { return { result: dashboardDoc }; }); 

Cree un objeto de actualización con los nombres de las propiedades, incluida la ruta de puntos necesaria. (“somekey.” + del ejemplo de OP), y luego use esa actualización.

 //the modification that's requested updateReq = { param2 : "val2_new", param3 : "val3_new" } //build a renamed version of the update request var update = {}; for(var field in updateReq){ update["somekey."+field] = updateReq[field]; } //apply the update without modifying fields not originally in the update db.collection.update({._id:...},{$set:update},{upsert:true},function(err,result){...}); 
  // where clause DBObject DBObject query = new BasicDBObject("_id", new ObjectId(id)); // modifications to be applied DBObject update = new BasicDBObject(); // set new values update.put("$set", new BasicDBObject("param2","value2")); // update the document collection.update(query, update, true, false); //3rd param->upsertFlag, 4th param->updateMultiFlag 

Si tiene múltiples campos para actualizar

  Document doc = new Document(); doc.put("param2","value2"); doc.put("param3","value3"); update.put("$set", doc); 

En Mongo 3.6 hay una función mergeObjects haciendo exactamente lo que necesita:

https://docs.mongodb.com/manual/reference/operator/aggregation/mergeObjects/