6

I have a project set up with playframework 2.2.0 and play2-reactivemongo 0.10.0-SNAPSHOT. I'd like to query for few documents by their ids, in a fashion similar to this:

def usersCollection = db.collection[JSONCollection]("users")
val ids: List[String] = /* fetched from somewhere else */
val query = ??
val users = usersCollection.find(query).cursor[User].collect[List]()

As a query I tried:

Json.obj("_id" -> Json.obj("$in" -> ids))                        // 1
Json.obj("_id.$oid" -> Json.obj("$in" -> ids))                   // 2
Json.obj("_id" -> Json.obj("$oid" -> Json.obj("$in" -> ids)))    // 3

for which first and second return empty lists and the third fails with error assertion 10068 invalid operator: $oid.

mcveat
  • 1,416
  • 15
  • 34
  • Why not `Json.obj("_id" -> Json.obj("$in" -> ids.map(BSONObjectID(_))))`? – Dominik Bucher Oct 31 '13 at 16:44
  • @Dom because then you have to have instance of `Write[BSONObjectID]` in implicit scope, and `play-reactivemongo` offers only partial one. Moreover, writing one feels not efficient, as you'll do conversion `BSONValue` -> `JsValue` -> `BSONValue` in this case. – mcveat Oct 31 '13 at 16:58
  • Ok, get what you mean, thanks. – Dominik Bucher Oct 31 '13 at 19:22

5 Answers5

3

NOTE: copy of my response on the ReactiveMongo mailing list.

First, sorry for the delay of my answer, I may have missed your question. Play-ReactiveMongo cannot guess on its own that the values of a Json array are ObjectIds. That's why you have to make a Json object for each id that looks like this: {"$oid": "526fda0f9205b10c00c82e34"}. When the ReactiveMongo Play plugin sees an object which first field is $oid, it treats it as an ObjectId so that the driver can send the right type for this value (BSONObjectID in this case.)

This is a more general problem actually: the JSON format does not match exactly the BSON one. That's the case for numeric types (BSONInteger, BSONLong, BSONDouble), BSONRegex, BSONDateTime, and BSONObjectID. You may find more detailed information in the MongoDB documentation: http://docs.mongodb.org/manual/reference/mongodb-extended-json/ .

Stephane Godbillon
  • 1,856
  • 13
  • 12
0

I managed to solve it with:

val objectIds = ids.map(id => Json.obj("$oid" -> id))
val query = Json.obj("_id" -> Json.obj("$in" -> objectIds))
usersCollection.find(query).cursor[User].collect[List]()

since play-reactivemongo format considers BSONObjectID only when "$oid" is followed by string

implicit object BSONObjectIDFormat extends PartialFormat[BSONObjectID] {
  def partialReads: PartialFunction[JsValue, JsResult[BSONObjectID]] = {
    case JsObject(("$oid", JsString(v)) +: Nil) => JsSuccess(BSONObjectID(v))
  }
  val partialWrites: PartialFunction[BSONValue, JsValue] = {
    case oid: BSONObjectID => Json.obj("$oid" -> oid.stringify)
  }
}

Still, I hope there is a cleaner solution. If not, I guess it makes it a nice pull request.

mcveat
  • 1,416
  • 15
  • 34
  • where to register `BSONObjectIDFormat ` ? – Harmeet Singh Taara Mar 09 '15 at 07:56
  • @HarmeetSinghTaara you can have it wherever you want. What you need to do is bringing it into scope wherever you need to do this. Like `import package.BSONObjectIDFormat` – mcveat Mar 09 '15 at 09:02
  • when i call `request.body.validate[User]` `validate` method, this will automatically? my request format as like `{ "_id":{"$oid":"54fd4b7084071e6a6ab13cee"} "name" : "Akka", "age" : 30, "created" : 1425886070013 }` – Harmeet Singh Taara Mar 09 '15 at 09:07
0

I'm wondering if transforming id to BSONObjectID isn't more secure this way :

val ids: List[String] = ???
val bsonObjectIds = ids.map(BSONObjectID.parse(_)).collect{case Success(t) => t}

this will only generate valid BSONObjectIDs (and discard invalid ones) If you do it this way :

val objectIds = ids.map(id => Json.obj("$oid" -> id))

your objectIds may not be valid ones depending on string id really being the stringify version of a BSONObjectID or not

fmasion
  • 71
  • 3
0

If you import play.modules.reactivemongo.json._ it work without any $oid formatters.

import play.modules.reactivemongo.json._
...
val ids: Seq[BSONObjectID] = ???
val selector = Json.obj("_id" -> Json.obj("$in" -> ids))
usersCollection.find(selector).cursor[User].collect[Seq]()
mgosk
  • 1,874
  • 14
  • 23
0

I tried with the following and it worked for me:

val listOfItems = BSONArray(51, 61)

val query = BSONDocument("_id" -> BSONDocument("$in" -> listOfItems))

val ruleListFuture = bsonFutureColl.flatMap(_.find(query, Option.empty[BSONDocument]).cursor[ResponseAccDataBean]().
      collect[List](-1, Cursor.FailOnError[List[ResponseAccDataBean]]()))
swapnil shashank
  • 877
  • 8
  • 11