javascript

node.js

mongodb

I have a field in mongodb that's a string. {"field": "some text"}, I want to convert them all into arrays. {"field": ["some text"]}

I know I can just loop through all the documents, get the field, then update, but I'm wondering if there's a cleaner way.

Thanks.

Solution 1

Nitin Garg's answer above almost works, except his example converts from a string to a hash, NOT a string to an array.

Taking into account Joel Harris's comments, the proper solution would look like:

db.jobs.find( { "jobLocationCity" : { $type : 2 } } ).snapshot().forEach( function (x) {
    x.jobLocationCity = [ jobLocationCity ];
    db.jobs.save(x);
});

Or if using db.eval:

function f() {
    db.jobs.find( { "jobLocationCity" : { $type : 2 } } ).snapshot().forEach( function (x) {
        x.jobLocationCity = [ jobLocationCity ];
        db.jobs.save(x);
    });
}
db.eval(f);

Solution 2

Starting in Mongo 4.2, db.collection.update() can accept an aggregation pipeline, finally allowing the update of a field based on its current value:

// { field: "some text" }
db.collection.update(
  {},
  [{ $set: { field: ["$field"] } }],
  { multi: true }
)
// { field: [ "some text" ] }
  • The first part {} is the match query, filtering which documents to update (in this case all documents).

  • The second part [{ $set: { field: { ["$field"] } } }] is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline). $set (alias of $addFields) is a new aggregation operator which in this case replaces the field's value (simply wrapping it into an array). Note how field is modified directly based on its own value ($field).

  • Don't forget { multi: true } (or to use updateMany), otherwise only the first matching document will be updated.

Solution 3

Actually, the find( { "jobLocationCity" : { $type : 2 } } ) will not work properly, because if you'll run update script next time, it will treat ['mystring'] elements again as string type.

You should use something like this to prevent it:

db.message_info.find( { "jobLocationCity" : { $type : 2 } }  ).snapshot().forEach(
  function (x) {
    if (!Array.isArray(x.jobLocationCity)){
        x.jobLocationCity = [ x.jobLocationCity  ];
        db.jobs.save(x);
    }
  }
)

see http://docs.mongodb.org/manual/reference/operators/

Solution 4

You could do it in a Reduce function of map/reduce to keep all the processing in mongodb. Essentially you would use map/reduce to put the results into a new collection and then you could copy them back to the old collection (or delete old one and rename the new one). This has the advantage of keeping everything inside of mongo.

Update: Another option might be for you to use db.eval for this. db.eval executes on the server so the updates would be done on the server without any traffic/latency.

I think the only other option is as you described - do it on the client by querying and updating each one.

Solution 5

try this instead

This is to change the type of a field from string to array in mongoDB

db.jobs.find( { "jobLocationCity" : { $type : 2 } } ).forEach( function (x) {
    x.jobLocationCity = {"Location":x.jobLocationCity};
    db.jobs.save(x);
});

see the link for $type's possible values

Solution 6

but I'm wondering if there's a cleaner way..

The short answer is no.

MongoDB does not have any single operation or command to perform a "change type".

The quickest way to do this is likely to use one of the drivers and make the change. You can use the shell and write a for loop, but in terms of raw speed, the other drivers may be faster.

That stated, the slowest part of the process is going to be loading all of the data from disk into memory to be changed and then flushing that data back to disk. This would be true even with a magic "change type" command.

Solution 7

I think that's the correct answer:

db.collection.update({},[{$set:{field:["$field"]}}],{ multi: true })

Solution 8

SOLVED for MongoDB 4.4.

db.products.find( { "images" : { $type : 2 } } ).forEach( function (x) { x.images = [ x.images ]; db.products.save(x); });