Given this document saved in MongoDB
{
_id : ...,
some_key: {
param1 : "val1",
param2 : "val2",
param3 : "val3"
}
}
An object with new information on param2
and param3
from the outside world needs to be saved
var new_info = {
param2 : "val2_new",
param3 : "val3_new"
};
I want to merge / overlay the new fields over the existing state of the object so that param1 doesn't get removed
Doing this
db.collection.update( { _id:...} , { $set: { some_key : new_info } }
Will lead to MongoDB is doing exactly as it was asked, and sets some_key to that value. replacing the old one.
{
_id : ...,
some_key: {
param2 : "val2_new",
param3 : "val3_new"
}
}
What is the way to have MongoDB update only new fields (without stating them one by one explicitly)? to get this:
{
_id : ...,
some_key: {
param1 : "val1",
param2 : "val2_new",
param3 : "val3_new"
}
}
I'm using the Java client, but any example will be appreciated
This question is related to
mongodb
mongodb-java
You can use dot-notation to access and set fields deep inside objects, without affecting the other properties of those objects.
Given the object you specified above:
> db.test.insert({"id": "test_object", "some_key": {"param1": "val1", "param2": "val2", "param3": "val3"}})
WriteResult({ "nInserted" : 1 })
We can update just some_key.param2
and 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"
}
}
You can delve as deep as you like. This is also useful for adding new properties to an object without affecting the existing ones.
use $set do this process
.update({"_id": args.dashboardId, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
.exec()
.then(dashboardDoc => {
return {
result: dashboardDoc
};
});
I had success doing it this way:
db.collection.update( { _id:...} , { $set: { 'key.another_key' : new_info } } );
I have a function that handles my profile updates dynamically
function update(prop, newVal) {
const str = `profile.${prop}`;
db.collection.update( { _id:...}, { $set: { [str]: newVal } } );
}
Note: 'profile' is specific to my implementation, it is just the string of the key that you would like to modify.
Starting Mongo 4.2
, db.collection.update()
can accept an aggregation pipeline, which allows using aggregation operators such as $addFields
, which outputs all existing fields from the input documents and newly added fields:
var new_info = { param2: "val2_new", param3: "val3_new" }
// { some_key: { param1: "val1", param2: "val2", param3: "val3" } }
// { some_key: { param1: "val1", param2: "val2" } }
db.collection.update({}, [{ $addFields: { some_key: new_info } }], { multi: true })
// { some_key: { param1: "val1", param2: "val2_new", param3: "val3_new" } }
// { some_key: { param1: "val1", param2: "val2_new", param3: "val3_new" } }
The first part {}
is the match query, filtering which documents to update (in this case all documents).
The second part [{ $addFields: { some_key: new_info } }]
is the update aggregation pipeline:
$addFields
.$addFields
performs exactly what you need: updating the object so that the new object will overlay / merge with the existing one:{ param2: "val2_new", param3: "val3_new" }
will be merged into the existing some_key
by keeping param1
untouched and either add or replace both param2
and param3
.Don't forget { multi: true }
, otherwise only the first matching document will be updated.
You could rather do a upsert, this operation in MongoDB is utilized to save document into collection. If document matches query criteria then it will perform update operation otherwise it will insert a new document into collection.
something similar as below
db.employees.update(
{type:"FT"},
{$set:{salary:200000}},
{upsert : true}
)
You can use $mergeObjects
in the aggregation based update. Something like
db.collection.update(
{ _id:...},
[{"$set":{
"some_key":{
"$mergeObjects":[
"$some_key",
new info or { param2 : "val2_new", param3 : "val3_new"}
]
}
}}]
)
More examples here
It looks like you can set isPartialObject which might accomplish what you want.
db.collection.update( { _id:...} , { $set: { some_key : new_info } }
to
db.collection.update( { _id: ..} , { $set: { some_key: { param1: newValue} } } );
Hope this help!
I tried findAndModify()
to update a particular field in a pre-existing object.
https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/
// 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
If you have multiple fields to be updated
Document doc = new Document();
doc.put("param2","value2");
doc.put("param3","value3");
update.put("$set", doc);
You should think about updating the object interchangeably and then simply store the object with the updated fields. Something like done below
function update(_id) {
return new Promise((resolve, reject) => {
ObjModel.findOne({_id}).exec((err, obj) => {
if(err) return reject(err)
obj = updateObject(obj, {
some_key: {
param2 : "val2_new",
param3 : "val3_new"
}
})
obj.save((err, obj) => {
if(err) return reject(err)
resolve(obj)
})
})
})
}
function updateObject(obj, data) {
let keys = Object.keys(data)
keys.forEach(key => {
if(!obj[key]) obj[key] = data[key]
if(typeof data[key] == 'object')
obj[key] = updateObject(obj[key], data[key])
else
obj[key] = data[key]
})
return obj
}
I solved it with my own function. If you want to update specified field in document you need to address it clearly.
Example:
{
_id : ...,
some_key: {
param1 : "val1",
param2 : "val2",
param3 : "val3"
}
}
If you want to update param2 only, it's wrong to do:
db.collection.update( { _id:...} , { $set: { some_key : new_info } } //WRONG
You must use:
db.collection.update( { _id:...} , { $set: { some_key.param2 : new_info } }
So i wrote a function something like that:
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'));
The best solution is to extract properties from object and make them flat dot-notation key-value pairs. You could use for example this library:
https://www.npmjs.com/package/mongo-dot-notation
It has .flatten
function that allows you to change object into flat set of properties that could be then given to $set modifier, without worries that any property of your existing DB object will be deleted/overwritten without need.
Taken from mongo-dot-notation
docs:
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
}
}
*/
And then it forms perfect selector - it will update ONLY given properties. EDIT: I like to be archeologist some times ;)
Mongo lets you update nested documents using a .
convention. Take a look: Updating nested documents in mongodb. Here's another question from the past about a merge update, like the one you're looking for I believe: MongoDB atomic update via 'merge' document
Make an update object with the property names including the necessary dot path. ("somekey."+ from OP's example), and then use that do the update.
//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){...});
You have to use Embedded Documents (stringfy the path object)
let update = {}
Object.getOwnPropertyNames(new_info).forEach(param => {
update['some_key.' + param] = new_info[param]
})
And so, in JavaScript you can use Spread Operator (...) to update
db.collection.update( { _id:...} , { $set: { ...update } }
Yeah, the best way is to convert the object notation to a flat key-value string representation, as mentioned in this comment: https://stackoverflow.com/a/39357531/2529199
I wanted to highlight an alternative method using this NPM library: https://www.npmjs.com/package/dot-object which lets you manipulate different objects using dot notation.
I used this pattern to programatically create a nested object property when accepting the key-value as a function variable, as follows:
const dot = require('dot-object');
function(docid, varname, varvalue){
let doc = dot.dot({
[varname]: varvalue
});
Mongo.update({_id:docid},{$set:doc});
}
This pattern lets me use nested as well as single-level properties interchangeably, and insert them cleanly into Mongo.
If you need to play around with JS Objects beyond just Mongo, especially on the client-side but have consistency when working with Mongo, this library gives you more options than the earlier mentioned mongo-dot-notation
NPM module.
P.S I originally wanted to just mention this as a comment but apparently my S/O rep isn't high enough to post a comment. So, not trying to muscle in on SzybkiSasza's comment, just wanted to highlight providing an alternative module.
Source: Stackoverflow.com