Search code examples
scalareactivemongoplay-reactivemongo

Basic Create-from-Json Method Walkthrough


I'm new to play, scala, and reactivemongo and was wondering if someone could explain to me the following code in easy terms to understand.

def createFromJson = Action.async(parse.json) { request =>
import play.api.libs.json.Reads._

val transformer: Reads[JsObject] =
  Reads.jsPickBranch[JsString](__ \ "name") and
    Reads.jsPickBranch[JsNumber](__ \ "age") and
    Reads.jsPut(__ \ "created", JsNumber(new java.util.Date().getTime())) reduce

request.body.transform(transformer).map { result =>
  collection.insert(result).map { lastError =>
    Logger.debug(s"Successfully inserted with LastError: $lastError")
    Created
  }
}.getOrElse(Future.successful(BadRequest("invalid json")))}

I know that it creates a user from a JSON user with name and age attributes. What I don't understand is the way that input JSON is read in this method. ALSO the concept of Action.async(par.json), request => getorElse, Future, etc.

ALSO any easier/simpler ways of writing this method would be greatly appreciated.

Thanks in advance!


Solution

  • I believe you found this code in a template I have made following excellent reactive mongo documentation.

    I feel a bit obliged to explain it. Let's run through the code.

    def createFromJson = Action.async(parse.json) { request =>
    

    The function createFromJson will return an Action (play stuff) that is asynchronous (returns a future of a result) that handles a body in json format. To do that it will use the request.

    Documentation: https://www.playframework.com/documentation/2.5.x/ScalaAsync

    A json can be anything that follows the json formats, for example an array a String, an object, ...

    Our transformer is going to take only the data that we are interested in from the json and will return a clean json object

    val transformer: Reads[JsObject] =
      Reads.jsPickBranch[JsString](__ \ "name") and
        Reads.jsPickBranch[JsNumber](__ \ "age") and
        Reads.jsPut(__ \ "created", JsNumber(new java.util.Date().getTime())) reduce
    

    As you see, it will pick the branch name as a string and the branch age as a number. It will also add to the final json object a field created with the time of creation.

    As you see we are not transforming it to a Person instance, it is just a JsObject instance as it is defined in

    val transformer: Reads[JsObject] ....
    

    Play offers you a few ways to handle json in a simpler way. This examples tries to show the power of manipulating directly the json values without converting to a model. For example if you have a case class

    case class Person(name: String, age: Int)
    

    You could create automatically a reads from it,

    val personReads: Person[Person] = Json.reads[Person]
    

    But to just store it in Mongo DB there is no reason to build this instance and then transform it to json again. Of course if you need to do some logic with the models before inserting them, you might need to create the model.

    Documentation:

    With this in mind the rest of the code should be clear

    request.body.transform(transformer).map { result =>
      collection.insert(result).map { lastError =>
        Logger.debug(s"Successfully inserted with LastError: $lastError")
        Created
      }
    }
    

    From the request, we take the body (a JsValue) we transform it into a JsObject (result) and we insert it in the collection. Insert returns a Future with the last error, when the Person is stored last error will be logged and a Created (201 code) will be returned to the client of the API.

    The last bit should be also clear by now

    }.getOrElse(Future.successful(BadRequest("invalid json")))
    

    If there is any problem parsing and transforming the json body of the request into our JsObject an already completed future with the result BadRequest (400 code) will be returned to the client. It is a future because Action.Async needs future of result as the return type.

    Enjoy scala.