1
votes

Using the ReactiveMongo driver I am trying to serialize a Map of potentially unknown keys/fields of mixed types Map[String, Any] into a BSONDocument. My question is quite similar to that asked here which refers to the ReactiveMongo documentation example of serializing maps here. Unfortunately the answer only seems to work for Maps of a consistent type whereas I need it for Any. My MapWriter is as follows:

implicit def ValueMapWriter[V](implicit vw: BSONWriter[V, _ <: BSONValue]): BSONDocumentWriter[Map[String, V]] =
    new BSONDocumentWriter[Map[String, V]] {
      def write(map: Map[String, V]): BSONDocument = {
        val elements = map.toStream.map {
          tuple =>
            tuple._1 -> vw.write(tuple._2)
        }
        BSONDocument(elements)
      }
    }

The following compiles and serializes just fine as all types are Ints

val allInts = Map("someInt" -> 1, "someOtherInt" -> 2)
val docFromMap: BSONDocument = BSON.writeDocument(allInts)    

However I cannot seem to massage it into something that will handle Map[String,Any]

val mixedTypes = Map("someDouble" -> 234.324, "someString" -> "abc", "someInt" -> 1)
val docFromMap: BSONDocument = BSON.writeDocument(mixedTypes)

results in:

Error:(134, 54) could not find implicit value for parameter writer: reactivemongo.bson.BSONWriter[scala.collection.immutable.Map[String,Any],reactivemongo.bson.BSONDocument]
val docFromMap: BSONDocument = BSON.writeDocument(mixedTypes)
                                                 ^

There is likely some fundamental concept of implicits that I'm missing in order to accomplish this, without explicitly knowing the types of each field in the map at compile time.

1
The implicit there is used for typeclasses, which is not magic, but as said requires consistent type, not Any. Either you can replace the map with something typesafe, or you can replace it by JsObject (kind of map with types that can be discovered from the JSON semantic) - cchantep

1 Answers

2
votes

Taking the advice from cchantep, I went down the road of making things more explicitly typesafe by matching the different types in the map and writing them to the document. This can certainly be expanded to handle nested maps and arrays. A good example of this can be seen here: JsBSONHandler Gist. For my scenario listed above with a flat map of mixed types, this works just fine for the value types that I care about:

def writeObject(obj: Map[String,Any]): BSONDocument = BSONDocument(obj.map(writePair))

def writePair(p: (String, Any)): (String, BSONValue) = (p._1, p._2 match     {
    case value: String  => BSONString(value)
    case value: Double  => BSONDouble(value)
    case value: Int     => BSONInteger(value)
    case value: Boolean => BSONBoolean(value)
    case value: Long    => BSONLong(value)
    case other => BSONNull
})

Usage:

val mixedTypes = Map("someDouble" -> 234.324, "someString" -> "abc", "someInt" -> 1)
val docFromMap: BSONDocument = writeObject(mixedTypes)