How to query with '$in' over '_id' in reactive mongo and play
Asked Answered
B

5

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.

Botanize answered 29/10, 2013 at 12:11 Comment(3)
Why not Json.obj("_id" -> Json.obj("$in" -> ids.map(BSONObjectID(_))))?Finnell
@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.Botanize
Ok, get what you mean, thanks.Finnell
A
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/ .

Adactylous answered 11/11, 2013 at 12:34 Comment(0)
B
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.

Botanize answered 29/10, 2013 at 15:1 Comment(3)
where to register BSONObjectIDFormat ?Defile
@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.BSONObjectIDFormatBotanize
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 }Defile
P
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

Peashooter answered 5/1, 2014 at 11:22 Comment(0)
S
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]()
Supersession answered 9/1, 2017 at 16:53 Comment(0)
O
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]]()))
Oslo answered 10/7, 2020 at 12:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.