202

I am trying to change the type of a field from within the mongo shell.

I am doing this...

db.meta.update(
  {'fields.properties.default': { $type : 1 }}, 
  {'fields.properties.default': { $type : 2 }}
)

But it's not working!

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
J. Quintas
  • 2,075
  • 2
  • 13
  • 8
  • If anyone is in the same situation I was, having to `toString` some document's field, [here's the little program I've made/used](https://github.com/tallesl/mongo-to-string). – talles Jan 11 '15 at 20:15

14 Answers14

230

The only way to change the $type of the data is to perform an update on the data where the data has the correct type.

In this case, it looks like you're trying to change the $type from 1 (double) to 2 (string).

So simply load the document from the DB, perform the cast (new String(x)) and then save the document again.

If you need to do this programmatically and entirely from the shell, you can use the find(...).forEach(function(x) {}) syntax.


In response to the second comment below. Change the field bad from a number to a string in collection foo.

db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) {   
  x.bad = new String(x.bad); // convert field to string
  db.foo.save(x);
});
user6039980
  • 3,108
  • 8
  • 31
  • 57
Gates VP
  • 44,957
  • 11
  • 105
  • 108
  • 1
    Any chance of an example - changing a field type from int to string (or vice versa), from the shell? – Alister Bulman Mar 16 '11 at 13:30
  • 34
    in case Int32->String, `new String(x.bad)` creates collection of Strings with 0-index-item `x.bad` value. Variant `""+x.bad`, described by Simone works as desired - creates String value instead of Int32 – Dao Jul 30 '12 at 16:36
  • The above code is converting the field data from double to array instead of double to string. My actual data was in this format :3.1 whereas simone code is working fine for me – Pankaj Khurana Apr 09 '14 at 09:30
  • 2
    Had a situation where I needed to convert the _id field as well as not conflict with other indexes: `db.questions.find({_id:{$type:16}}).forEach( function (x) { db.questions.remove({_id:x._id},true); x._id = ""+x._id; db.questions.save(x); });` – Matt Molnar Dec 03 '15 at 16:12
  • @SundarBons yes you are re-writing a field across your database, this is a big deal no matter how you do it. If you were using SQL and this was a big table you would probably have to take some down time. – Gates VP Jun 28 '16 at 18:16
  • already use this, it will stop without completing all, I will not recommend this – Aljohn Yamaro Apr 25 '18 at 05:51
  • IMO this should not be the accepted answer. The answer of Simone is the one that works. If you use `new String(obj.field)` you will get a dict and mess up your mongo collection. – tharndt Oct 25 '18 at 12:39
  • Sadly, Simone's answer came about 6 months after mine. The only person who can change the "checkmark" is the original asker. FWIW, they both use the same basic syntax, with Simone's addressing two very specific cases. I would not use any of these methods on a Production database with any amount of real data. – Gates VP Oct 28 '18 at 17:00
  • This works but damn! SOOOO SLOOOOOW! MongoDB needs to up its game with these kind of update operations. In SQL I could do an update that references values from the same row for thousands of records in only a few ms, like `UPDATE sometable SET name = CONCAT(first, ' ', last)` ... but in Mongo it's about the same time ... per record. – ClubbedAce Nov 13 '18 at 19:51
  • Yes, @ClubbedAce, it is very slow. You are effectively loading every entry in the collection and then re-saving this. I know it looks like you could do this quickly in SQL, but if you had a 3TB table, the change you list would likely lock the table and would also take a long a time. To be clear, I would never use this method in a production database with any significant amount of data. If this data is actively being used, you need to migrate the data to a new field and update code, etc. Data safety is a lot of work. – Gates VP Nov 14 '18 at 19:34
  • 3
    `.save is not a function` facing this error – Muhammad Awais Jun 12 '22 at 07:23
173

Convert String field to Integer:

db.db-name.find({field-name: {$exists: true}}).forEach(function(obj) { 
    obj.field-name = new NumberInt(obj.field-name);
    db.db-name.save(obj);
});

Convert Integer field to String:

db.db-name.find({field-name: {$exists: true}}).forEach(function(obj) {
    obj.field-name = "" + obj.field-name;
    db.db-name.save(obj);
});
Yaroslav Admin
  • 13,880
  • 6
  • 63
  • 83
Simone
  • 1,747
  • 1
  • 10
  • 2
  • This is great - do you know how you'd convert a string (think currency like '1.23') to the integer 123? I assume you'd have to parse it as a float or decimal, multiply it by 100, then save it as an integer, but I can't find the right docs to do this. Thanks! – Brian Armstrong Feb 23 '12 at 05:59
  • Actually this working is good. But I have an application running with mongoid 2.4.0-stable which has fields such as field: customer_count, type: Integer & a validation as validates_numericality_of :customer_count which was working fine. Now when I am upgrading to mongoid to 3.0.16, when I assign a string value it automatically converts it to 0. without an error. I want to throw an error on wrong data assignment, this behavior turning out strange for me. – Swapnil Chincholkar Feb 01 '13 at 11:37
  • 4
    I ran this and got the error: Error: could not convert string to integer (shell):1 – Mittenchops Mar 21 '13 at 21:04
  • And if you need to convert string (or "normal" 32-bit integer) to 64-bit integer, use ```NumberLong``` as here: ```db.db-name.find({field-name : {$exists : true}}).forEach( function(obj) { obj.field-name = new NumberLong(obj.field-name); db.db-name.save(obj); } );``` – boryn Feb 27 '14 at 11:02
  • This worked for me not only for numbers but for ObjectId data types too. – Danielo515 Jun 23 '16 at 07:51
  • Hello simone, I have tried this but it is not working for me. I am using mongodb 3.4 in linux 14 system. – JayminLimbachiya Mar 18 '19 at 12:49
  • "Convert Integer field to String:" works also for Double conversion to String – Mykeul Apr 06 '21 at 06:45
98

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

// { a: "45", b: "x" }
// { a:  53,  b: "y" }
db.collection.updateMany(
  { a : { $type: 1 } },
  [{ $set: { a: { $toString: "$a" } } }]
)
// { a: "45", b: "x" }
// { a: "53", b: "y" }
  • The first part { a : { $type: 1 } } is the match query:

    • It filters which documents to update.
    • In this case, since we want to convert "a" to string when its value is a double, this matches elements for which "a" is of type 1 (double)).
    • This table provides the code representing the different possible types.
  • The second part [{ $set: { a: { $toString: "$a" } } }] is the update aggregation pipeline:

    • Note the squared brackets signifying that this update query uses an aggregation pipeline.
    • $set is a new aggregation operator (Mongo 4.2) which in this case modifies a field.
    • This can be simply read as "$set" the value of "a" to "$a" converted "$toString".
    • What's really new here, is being able in Mongo 4.2 to reference the document itself when updating it: the new value for "a" is based on the existing value of "$a".
    • Also note "$toString" which is a new aggregation operator introduced in Mongo 4.0.

In case your cast isn't from double to string, you have the choice between different conversion operators introduced in Mongo 4.0 such as $toBool, $toInt, ...

And if there isn't a dedicated converter for your targeted type, you can replace { $toString: "$a" } with a $convert operation: { $convert: { input: "$a", to: 2 } } where the value for to can be found in this table:

db.collection.updateMany(
  { a : { $type: 1 } },
  [{ $set: { a: { $convert: { input: "$a", to: 2 } } } }]
)
starball
  • 20,030
  • 7
  • 43
  • 238
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
  • 3
    `db.collection.updateMany( { a : { $type: 1 } }, [{ $set: { a: { $toString: "$a" } } }] )` - the `multi : true` can be avoided using `updateMany` – Vasim Jan 12 '20 at 10:15
  • 3
    As of 2020, using $convert should be the correct method for this as it should be a lot more efficient (and easier to use to boot). – KhalilRavanna Jan 30 '20 at 02:45
  • As of Mongo 4.0 there are shorthand operators: "...In addition to $convert, MongoDB provides the following aggregation operators as shorthand when the default "onError" and "onNull" behavior is acceptable". https://www.mongodb.com/docs/manual/reference/operator/aggregation/convert/#definition – Paul Apr 15 '22 at 18:21
  • 1
    This converted a double value of `6503856832666` to `"6.50386e+12"` :( – Shashank Agrawal Dec 27 '22 at 12:27
  • 2
    @ShashankAgrawal to convert a large double value to a string, avoiding scientific notation, you first have to convert it to a Long – neilireson Feb 13 '23 at 12:24
47

For string to int conversion.

db.my_collection.find().forEach( function(obj) {
    obj.my_value= new NumberInt(obj.my_value);
    db.my_collection.save(obj);
});

For string to double conversion.

    obj.my_value= parseInt(obj.my_value, 10);

For float:

    obj.my_value= parseFloat(obj.my_value);
David Dehghan
  • 22,159
  • 10
  • 107
  • 95
  • 2
    I would recommend also specifying the `radix` - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt – Russ Cam May 09 '14 at 10:24
  • 5
    **Watch out**, I tested this with Robomongo, and this resulted in type 1: double. Had to use `new NumberInt()` – Daniel F Nov 08 '14 at 19:50
  • Daniel, ur solution is not okay... david's solution is ok – Rajib Mar 09 '17 at 09:57
29
db.coll.find().forEach(function(data) {
    db.coll.update({_id:data._id},{$set:{myfield:parseInt(data.myfield)}});
})
Russell
  • 299
  • 3
  • 2
21

all answers so far use some version of forEach, iterating over all collection elements client-side.

However, you could use MongoDB's server-side processing by using aggregate pipeline and $out stage as :

the $out stage atomically replaces the existing collection with the new results collection.

example:

db.documents.aggregate([
         {
            $project: {
               _id: 1,
               numberField: { $substr: ['$numberField', 0, -1] },
               otherField: 1,
               differentField: 1,
               anotherfield: 1,
               needolistAllFieldsHere: 1
            },
         },
         {
            $out: 'documents',
         },
      ]);
user3616725
  • 3,485
  • 1
  • 18
  • 27
  • 5
    I don't know why this isn't upvoted more. Row by row operations on large data sets are murder on performance – Alf47 Dec 03 '18 at 13:14
12

To convert a field of string type to date field, you would need to iterate the cursor returned by the find() method using the forEach() method, within the loop convert the field to a Date object and then update the field using the $set operator.

Take advantage of using the Bulk API for bulk updates which offer better performance as you will be sending the operations to the server in batches of say 1000 which gives you a better performance as you are not sending every request to the server, just once in every 1000 requests.

The following demonstrates this approach, the first example uses the Bulk API available in MongoDB versions >= 2.6 and < 3.2. It updates all the documents in the collection by changing all the created_at fields to date fields:

var bulk = db.collection.initializeUnorderedBulkOp(),
    counter = 0;

db.collection.find({"created_at": {"$exists": true, "$type": 2 }}).forEach(function (doc) {
    var newDate = new Date(doc.created_at);
    bulk.find({ "_id": doc._id }).updateOne({ 
        "$set": { "created_at": newDate}
    });

    counter++;
    if (counter % 1000 == 0) {
        bulk.execute(); // Execute per 1000 operations and re-initialize every 1000 update statements
        bulk = db.collection.initializeUnorderedBulkOp();
    }
})
// Clean up remaining operations in queue
if (counter % 1000 != 0) { bulk.execute(); }

The next example applies to the new MongoDB version 3.2 which has since deprecated the Bulk API and provided a newer set of apis using bulkWrite():

var bulkOps = [];

db.collection.find({"created_at": {"$exists": true, "$type": 2 }}).forEach(function (doc) { 
    var newDate = new Date(doc.created_at);
    bulkOps.push(         
        { 
            "updateOne": { 
                "filter": { "_id": doc._id } ,              
                "update": { "$set": { "created_at": newDate } } 
            }         
        }           
    );     
})

db.collection.bulkWrite(bulkOps, { "ordered": true });
chridam
  • 100,957
  • 23
  • 236
  • 235
  • 1
    Great answer, the bulk method runs about 100x quicker for me even though it still appears to be a synchronous call. – Matthew Read Mar 11 '19 at 16:19
3

To convert int32 to string in mongo without creating an array just add "" to your number :-)

db.foo.find( { 'mynum' : { $type : 16 } } ).forEach( function (x) {   
  x.mynum = x.mynum + ""; // convert int32 to string
  db.foo.save(x);
});
Giulio Roggero
  • 1,692
  • 1
  • 16
  • 11
3

What really helped me to change the type of the object in MondoDB was just this simple line, perhaps mentioned before here...:

db.Users.find({age: {$exists: true}}).forEach(function(obj) {
    obj.age = new NumberInt(obj.age);
    db.Users.save(obj);
});

Users are my collection and age is the object which had a string instead of an integer (int32).

Felipe
  • 101
  • 10
2
You can easily convert the string data type to numerical data type.
Don't forget to change collectionName & FieldName.
for ex : CollectionNmae : Users & FieldName : Contactno.

Try this query..

db.collectionName.find().forEach( function (x) {
x.FieldName = parseInt(x.FieldName);
db.collectionName.save(x);
});
Amarendra Kumar
  • 858
  • 9
  • 16
1

I need to change datatype of multiple fields in the collection, so I used the following to make multiple data type changes in the collection of documents. Answer to an old question but may be helpful for others.

db.mycoll.find().forEach(function(obj) { 

    if (obj.hasOwnProperty('phone')) {
        obj.phone = "" + obj.phone;  // int or longint to string
    }

    if (obj.hasOwnProperty('field-name')) {
     obj.field-name = new NumberInt(obj.field-name); //string to integer
    }

    if (obj.hasOwnProperty('cdate')) {
        obj.cdate = new ISODate(obj.cdate); //string to Date
    }

    db.mycoll.save(obj); 
});
Aakash
  • 226
  • 2
  • 14
1

demo change type of field mid from string to mongo objectId using mongoose

 Post.find({}, {mid: 1,_id:1}).exec(function (err, doc) {
             doc.map((item, key) => {
                Post.findByIdAndUpdate({_id:item._id},{$set:{mid: mongoose.Types.ObjectId(item.mid)}}).exec((err,res)=>{
                    if(err) throw err;
                    reply(res);
                });
            });
        });

Mongo ObjectId is just another example of such styles as

Number, string, boolean that hope the answer will help someone else.

Pang
  • 9,564
  • 146
  • 81
  • 122
Tran Hoang Hiep
  • 544
  • 5
  • 12
1

The above answers almost worked but had a few challenges-

Problem 1: db.collection.save no longer works in MongoDB 5.x

For this, I used replaceOne().

Problem 2: new String(x.bad) was giving exponential number

I used "" + x.bad as suggested above.

My version:

let count = 0;

db.user
    .find({
        custID: {$type: 1},
    })
    .forEach(function (record) {
        count++;

        const actualValue = record.custID;
        record.custID = "" + record.custID;
        console.log(`${count}. Updating User(id:${record._id}) from old id [${actualValue}](${typeof actualValue}) to [${record.custID}](${typeof record.custID})`)

        db.user.replaceOne({_id: record._id}, record);
    });

And for millions of records, here are the output (for future investigation/reference)-

enter image description here

Shashank Agrawal
  • 25,161
  • 11
  • 89
  • 121
0

I use this script in mongodb console for string to float conversions...

db.documents.find({ 'fwtweaeeba' : {$exists : true}}).forEach( function(obj) { 
        obj.fwtweaeeba = parseFloat( obj.fwtweaeeba ); 
        db.documents.save(obj); } );    

db.documents.find({ 'versions.0.content.fwtweaeeba' : {$exists : true}}).forEach( function(obj) { 
        obj.versions[0].content.fwtweaeeba = parseFloat( obj.versions[0].content.fwtweaeeba ); 
        db.documents.save(obj); } );

db.documents.find({ 'versions.1.content.fwtweaeeba' : {$exists : true}}).forEach( function(obj) { 
        obj.versions[1].content.fwtweaeeba = parseFloat( obj.versions[1].content.fwtweaeeba );  
        db.documents.save(obj); } );

db.documents.find({ 'versions.2.content.fwtweaeeba' : {$exists : true}}).forEach( function(obj) { 
        obj.versions[2].content.fwtweaeeba = parseFloat( obj.versions[2].content.fwtweaeeba );  
        db.documents.save(obj); } );

And this one in php)))

foreach($db->documents->find(array("type" => "chair")) as $document){
    $db->documents->update(
        array('_id' => $document[_id]),
        array(
            '$set' => array(
                'versions.0.content.axdducvoxb' => (float)$document['versions'][0]['content']['axdducvoxb'],
                'versions.1.content.axdducvoxb' => (float)$document['versions'][1]['content']['axdducvoxb'],
                'versions.2.content.axdducvoxb' => (float)$document['versions'][2]['content']['axdducvoxb'],
                'axdducvoxb' => (float)$document['axdducvoxb']
            )
        ),
        array('$multi' => true)
    );


}
user3469031
  • 21
  • 1
  • 6