7

I have a BasicDBList that has been persisted into the database. I am now reading the data and trying to convert the list to an immutable scala list as shown:

val collection = mongoFactory.getCollection("tokens")    
val appId = MongoDBObject("appId" -> id)
val appDBObject = collection.findOne(appId) 
val scope: List[String] = appDBObject.get("scope").asInstanceOf[List[String]]

However, I am getting a class cast exception saying it is not possible to cast a BasicDBList to a Scala immutable list.

I have tried various combinations, such as converting to a map, etc. Nothing seems to work.

gofeddy
  • 579
  • 8
  • 20

1 Answers1

13

Because MongoDB stores Arrays the same way JavaScript does --- as an object with integer keys indicating their index --- BasicDBList is necessary internally to represent the object coming off of the wire. As such, currently Casbah doesn't automatically represent it as a Scala list.... BasicDBList is a HashMap, not a List.

HOWEVER, internally Casbah does provide implicit conversions to let you treat BasicDBList as a LinearSeq[AnyRef]; LinearSeq is a bit different on the type tree than List but a more appropriate type for a variety of reasons. Unfortunately you can't cast with implicit conversions.

For now, what I recommend is that you get the item as a DBList, and then either type annotate it as a LinearSeq which will use the implicit, or simply call toList on it (The implicit will provide the toList method).

scala> val l = MongoDBList("foo", "bar", "baz")
l: com.mongodb.BasicDBList = [ "foo" , "bar" , "baz"]

scala> val obj = MongoDBObject("list" -> l)
obj: com.mongodb.casbah.commons.Imports.DBObject = { "list" : [ "foo" , "bar" , "baz"]}

scala> obj.as[BasicDBList]("list")
res8: com.mongodb.casbah.Imports.BasicDBList = [ "foo" , "bar" , "baz"]

scala> obj.as[BasicDBList]("list").toList
res9: List[AnyRef] = List(foo, bar, baz)

The as[T]: T and getAs[T]: Option[T] methods are preferable, incidentally, to casting as they have some trickery inside to do type massaging. The next release of Casbah will include code so that if you ask for a Seq, List, etc and it's a DBList as and getAs will automatically convert them to the type you asked for.

Brendan W. McAdams
  • 9,379
  • 3
  • 41
  • 31
  • Thanks for that reply. I had tried this approach as well. But, I was not able to figure out how to convert List[AnyRef] to List[String], since I want the result as a list of Strings. Might be simple, but not sure how. – gofeddy Apr 25 '11 at 16:40
  • 7
    @venividivamos `list collect { case s: String => s }` will get all `String` out of a `List[AnyRef]` and return a `List[String]` with them. – Daniel C. Sobral Apr 25 '11 at 16:52
  • 1
    Yup, @DCSobrals answer is about the best bet. Because you can't "type" collections in MongoDB there's no way to guarantee short of walking the collection that the contents are a specific type. Collecting them would be safest. In fact, I'm going to steal this approach for the next release to 'attempt' to cast the collection all to a given type. – Brendan W. McAdams Apr 25 '11 at 16:54
  • @Daniel Exactly what I wanted. Thanks a lot. – gofeddy Apr 25 '11 at 19:01
  • 3
    Is this still valid as of 2.1.5.0? I get a compile error that "value toList is not a member of com.mongodb.BasicDBList" – Ben Dilts Jun 11 '12 at 22:40
  • @DanielC.Sobral how would it work if the array contains a tuple, e.g. Array[(String, Double)] or a Map of the same? – andyczerwonka Sep 29 '13 at 15:55
  • @andyczerwonka `list collect { case arr: Array[_] => arr collect { case (s: String, d: Double) => (s, d) } }`, or something like that. – Daniel C. Sobral Sep 30 '13 at 04:09
  • Side question - how would you convert the `MongoDBList` into a `MongoDBObject` that could be passed into `collection.find(...)` or `collection.insert(...)`? – Kevin Meredith Oct 15 '13 at 13:28
  • @DanielC.Sobral Thanks for mentioning `collect`. Made me realize even `map` takes partial functions. – akauppi Nov 18 '14 at 13:56