Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mongodb get _id as string in find query

Here I have created a collection with a single document

db.getCollection('example').insert({"example":1});

I have tried to use Projection, and I get back the _id.

db.getCollection('example').find({"example":1},{"_id":1});

{
    "_id" : ObjectId("562a6300bbc948a4315f3abc")
}

However, I need the below output as shown below.

  1. id and not _id
  2. ObjectId("562a6300bbc948a4315f3abc") vs "562a6300bbc948a4315f3abc"

    { "id" : "562a6300bbc948a4315f3abc" }

Although I can process #1 and #2 on my app server(PHP based) to get the desired ouput, I am looking if there is a way to get the expected result on querying from mongo itself

like image 328
Faiz Mohamed Haneef Avatar asked Oct 23 '15 16:10

Faiz Mohamed Haneef


3 Answers

MongoDB 4.0 adds the $convert aggregation operator and the $toString alias which allows you to do exactly that:

db.getCollection('example').aggregate([
  { "$match": { "example":1 } },
  { "$project": { "_id": { "$toString": "$_id" } } }
])

A main usage would most likely be though to use the _id value as a "key" in a document.

db.getCollection('example').insertOne({ "a": 1, "b": 2 })

db.getCollection('example').aggregate([
  { "$replaceRoot": {
    "newRoot": {
      "$arrayToObject": [
        [{ 
          "k": { "$toString": "$_id" },
          "v": {
            "$arrayToObject": {
              "$filter": {
                "input": { "$objectToArray": "$$ROOT" },
                "cond": { "$ne": ["$$this.k", "_id"] }
              }
            }
          }
        }] 
      ]
    }
  }}
])

Which would return:

{ 
  "5b06973e7f859c325db150fd" : { "a" : 1, "b" : 2 }
}

Which clearly shows the string, as does the other example.

Generally though there is usually a way to do "transforms" on the cursor as documents are returned from the server. This is usually a good thing since an ObjectId is a 12-byte binary representation as opposed to a 24 character hex "string" which takes a lot more space.

The shell has a .map() method

db.getCollection('example').find().map(d => Object.assign(d, { _id: d._id.valueOf() }) )

And NodeJS has a Cursor.map() which can do much the same thing:

let cursor = db.collection('example').find()
    .map(( _id, ...d }) => ({ _id: _id.toString(), ...d }));

while ( await cursor.hasNext() ) {
  let doc = cursor.next();
  // do something
})

And the same method exists in other drivers as well ( just not PHP ), or you can just iterate the cursor and transform the content as is more likely the best thing to do.


In fact, whole cursor results can be reduced into a single object with great ease by simply adding to any cursor returning statement, when working in the shell

.toArray().reduce((o,e) => { 
  var _id = e._id;
  delete e._id;
  return Object.assign(o, { [_id]: e })
},{ })

Or for full ES6 JavaScript supporting environments like nodejs:

.toArray().reduce((o,({ _id, ...e })) =>  ({ ...o, [_id]: e }),{ })

Really simple stuff without the complexity of what needs to process in the aggregation framework. And very possible in any language by much the same means.

like image 197
Neil Lunn Avatar answered Oct 05 '22 23:10

Neil Lunn


You need to use the .aggregate() method.

db.getCollection('example').aggregate([ { "$project": { "_id": 0, "id": "$_id" } } ]);

Which yields:

{ "id" : ObjectId("562a67745488a8d831ce2e35") }

or using the .str property.

db.getCollection('example').find({"example":1},{"_id":1}).map(function(doc) { 
    return {'id': doc._id.str }
})

Which returns:

[ { "id" : "562a67745488a8d831ce2e35" } ]

Well if you are using the PHP driver you can do something like this:

$connection = new MongoClient();
$db = $connection->test;
$col = $db->example;
$cursor = $col->find([], ["_id" => 1]);
foreach($cursor as $doc) { print_r(array("id" => $doc["_id"])); } 

Which yields:

Array
(
    [id] => MongoId Object
        (
            [$id] => 562a6c60f850734c0c8b4567
        )

)

Or using again the MongoCollection::aggregate method.

$result = $col->aggregate(array(["$project" => ["id" => "$_id", "_id" => 0]]))

Then using the foreach loop:

Array
(
    [_id] => MongoId Object
        (
            [$id] => 562a6c60f850734c0c8b4567
        )

)
like image 31
styvane Avatar answered Oct 06 '22 00:10

styvane


One simple solution for traversing MongoCursor on PHP side is to use Generators as well as foreach or array_map($function, iterator_to_array($cursor)). Example:

function map_traversable(callable $mapper, \Traversable $iterator) {
    foreach($iterator as $val) {
        yield $mapper($val);
    }
}

You can meet more at PHP documentation about generators syntax.

So, now you can use/reuse it (or similar implementation) for any propose of "projecting" your data on PHP side with any amount of mapping (just like pipeline does in aggregate) but with fewer iterations amount. And this solution is pretty convenient for OOP in a case of reusing your map functions.

UPD: Just for your case example below:

$cursor = $db->getCollection('example')->find(["example":1],["_id":1]);
$mapper = function($record) {
    return array('id' => (string) $record['_id']); //see \MongoId::__toString()
}
$traversableWithIdAsStringApplied = map_traversable($mapper, $cursor);
//...

now you can proceed with more mappings applied to $traversableWithIdAsStringApplied or use just iterator_to_array for simple array retrieving.

like image 34
lazycommit Avatar answered Oct 05 '22 23:10

lazycommit