Search code examples
jsonscalaplayframeworkreactivemongoplay-reactivemongo

How to convert JSON String to a BSONDocument


I have the following function that uses the reactivemongo driver and actually does a good job writing to the database.

def writeDocument() = {
    val document = BSONDocument(
      "firstName" -> "Stephane",
      "lastName" -> "Godbillon",
      "age" -> 29)

    val future = collection.insert(document)

    future.onComplete {
      case Failure(e) => throw e
      case Success(result) => {
        println("successfully inserted document with result = " + result)
      }
    }
  }

But the limitation of that function is that the JSON is hardcoded into a BSONDocument. How can I change it so that I can pass any JSON String into the function?

Question in short: How do I convert a JSON String into a BSONDocument?

Update 2:

package controllers

//import play.api.libs.json._
//import reactivemongo.bson._
//import play.api.libs.json.Json

import scala.util.{Success, Failure}
import reactivemongo.api._
//import scala.concurrent.ExecutionContext.Implicits.global


import play.modules.reactivemongo.json.collection._
import reactivemongo.play.json._

object Mongo {

  //val collection = connect()

  def collection: JSONCollection = {
    val driver = new MongoDriver
    val connection = driver.connection(List("localhost"))
    val db = connection("superman")
    db.collection[JSONCollection]("IncomingRequests")
  }


  // TODO: Make this work with any JSON String
  def writeDocument() = {

    val jsonString = """{
                       | "guid": "alkshdlkasjd-ioqweuoiquew-123132",
                       | "title": "Hello-2016",
                       | "year": 2016,
                       | "action": "POST",
                       | "start": "2016-12-20",
                       | "stop": "2016-12-30"}"""


    val document = Json.parse(jsonString)
    val future = collection.insert(document)
    future.onComplete {
      case Failure(e) => throw e
      case Success(result) => {
        println("successfully inserted document with result = " + result)
      }
    }
  }

}

The problem now is that import reactivemongo.play.json._ is treated as an unused import (on my IntelliJ) and I still get the following error

[info] Compiling 9 Scala sources and 1 Java source to /Users/superman/target/scala-2.11/classes...
[error] /Users/superman/app/controllers/Mongo.scala:89: No Json serializer as JsObject found for type play.api.libs.json.JsValue. Try to implement an implicit OWrites or OFormat for this type.
[error] Error occurred in an application involving default arguments.
[error]     val future = collection.insert(document)
[error]                                   ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
[error] application - 

! @6oo00g47n - Internal server error, for (POST) [/validateJson] ->

play.sbt.PlayExceptions$CompilationException: Compilation error[No Json serializer as JsObject found for type play.api.libs.json.JsValue. Try to implement an implicit OWrites or OFormat for this type.
Error occurred in an application involving default arguments.]
        at play.sbt.PlayExceptions$CompilationException$.apply(PlayExceptions.scala:27) ~[na:na]
        at play.sbt.PlayExceptions$CompilationException$.apply(PlayExceptions.scala:27) ~[na:na]
        at scala.Option.map(Option.scala:145) ~[scala-library-2.11.6.jar:na]
        at play.sbt.run.PlayReload$$anonfun$taskFailureHandler$1.apply(PlayReload.scala:49) ~[na:na]
        at play.sbt.run.PlayReload$$anonfun$taskFailureHandler$1.apply(PlayReload.scala:44) ~[na:na]
        at scala.Option.map(Option.scala:145) ~[scala-library-2.11.6.jar:na]
        at play.sbt.run.PlayReload$.taskFailureHandler(PlayReload.scala:44) ~[na:na]
        at play.sbt.run.PlayReload$.compileFailure(PlayReload.scala:40) ~[na:na]
        at play.sbt.run.PlayReload$$anonfun$compile$1.apply(PlayReload.scala:17) ~[na:na]
        at play.sbt.run.PlayReload$$anonfun$compile$1.apply(PlayReload.scala:17) ~[na:na]

Solution

  • First, you could serialize your model classes as BSON using reactivemongo. Check docs to see how.

    If you want to make a BSONDocument from String through play json you can use

    val playJson: JsValue = Json.parse(jsonString)
    val bson: BSONDocument = play.modules.reactivemongo.json.BSONFormats.reads(playJson).get
    

    Edit

    I found more in the docs here:

    http://reactivemongo.org/releases/0.11/documentation/tutorial/play2.html

    you can import those two

    import reactivemongo.play.json._
    import play.modules.reactivemongo.json.collection._
    

    Instead of using the default Collection implementation (which interacts with BSON structures + BSONReader/BSONWriter), we use a specialized implementation that works with JsObject + Reads/Writes.

    So you create specialized collection like this (must be def, not val):

    def collection: JSONCollection = db.collection[JSONCollection]("persons")
    

    and from now on you can use it with play json, instead of BSON, so simply passing in Json.parse(jsonString) as a document to insert should work. You can see more examples in the link.

    Edit 2 I got your code to compile:

    package controllers

    import play.api.libs.concurrent.Execution.Implicits._
    import play.api.libs.json._
    import play.modules.reactivemongo.json.collection.{JSONCollection, _}
    import reactivemongo.api.MongoDriver
    import reactivemongo.play.json._
    import play.api.libs.json.Reads._
    
    import scala.util.{Failure, Success}
    
    
    object Mongo {
    
      def collection: JSONCollection = {
        val driver = new MongoDriver
        val connection = driver.connection(List("localhost"))
        val db = connection("superman")
        db.collection[JSONCollection]("IncomingRequests")
      }
    
      def writeDocument() = {
    
       val jsonString = """{
                           | "guid": "alkshdlkasjd-ioqweuoiquew-123132",
                           | "title": "Hello-2016",
                           | "year": 2016,
                           | "action": "POST",
                           | "start": "2016-12-20",
                           | "stop": "2016-12-30"}"""
    
    
        val document = Json.parse(jsonString).as[JsObject]
        val future = collection.insert(document)
        future.onComplete {
          case Failure(e) => throw e
          case Success(result) =>
            println("successfully inserted document with result = " + result)
        }
      }
    }
    

    the important import is

    import play.api.libs.json.Reads._
    

    and you need JsObject, not just any JsValue

    val document = Json.parse(jsonString).as[JsObject]