Interacting with JSON

JSON, as we discovered in previous chapters, is becoming the de-facto language for communicating structured data over HTTP. If you develop a web application or a web API, it is likely that you will have to consume or emit JSON, or both.

In Chapter 7, Web APIs, we learned how to parse JSON through json4s. The Play framework includes its own JSON parser and emitter. Fortunately, it behaves in much the same way as json4s.

Let's imagine that we are building an API that summarizes information about GitHub repositories. Our API will emit a JSON array listing a user's repositories when queried about a specific user (much like the GitHub API, but with just a subset of fields).

Let's start by defining a model for the repository. In Play applications, models are normally stored in the folder app/models, in the models package:

// app/models/Repo.scala

package models

case class Repo (
  val name:String,
  val language:String,
  val isFork: Boolean,
  val size: Long
)

Let's add a route to our application that serves arrays of repos for a particular user. In conf/routes, add the following line:

// conf/routes
GET   /api/repos/:username       controllers.Api.repos(username)

Let's now implement the framework for the controller. We will create a new controller for our API, imaginatively called Api. For now, we will just have the controller return dummy data. This is what the code looks like (we will explain the details shortly):

// app/controllers/Api.scala
package controllers
import play.api._
import play.api.mvc._
import play.api.libs.json._

import models.Repo

class Api extends Controller {

  // Some dummy data.
  val data = List[Repo](
    Repo("dotty", "Scala", true, 14315),
    Repo("frontend", "JavaScript", true, 392)
  )

  // Typeclass for converting Repo -> JSON
  implicit val writesRepos = new Writes[Repo] {
    def writes(repo:Repo) = Json.obj(
      "name" -> repo.name,
      "language" -> repo.language,
      "is_fork" -> repo.isFork,
      "size" -> repo.size
    )
  }

  // The controller
  def repos(username:String) = Action {
    
    val repoArray = Json.toJson(data) 
    // toJson(data) relies on existence of 
    // `Writes[List[Repo]]` type class in scope

    Ok(repoArray)
  }
}

If you point your web browser to 127.0.0.1:9000/api/repos/odersky, you should now see the following JSON object:

[{"name":"dotty","language":"Scala","is_fork":true,"size":14315},{"name":"frontend","language":"JavaScript","is_fork":true,"size":392}]

The only tricky part of this code is the conversion from Repo to JSON. We call Json.toJson on data, an instance of type List[Repo]. The toJson method relies on the existence of a type class Writes[T] for the type T passed to it.

The Play framework makes extensive use of type classes to define how to convert models to specific formats. Recall that we learnt how to write type classes in the context of SQL and MongoDB. The Play framework's expectations are very similar: for the Json.toJson method to work on an instance of type Repo, there must be a Writes[Repo] implementation available that specifies how to transform Repo objects to JSON.

In the Play framework, the Writes[T] type class defines a single method:

trait Writes[T] {
  def writes(obj:T):Json
}

Writes methods for built-in simple types and for collections are already built into the Play framework, so we do not need to worry about defining Writes[Boolean], for instance.

The Writes[Repo] instance is commonly defined either directly in the controller, if it is just used for that controller, or in the Repo companion object, where it can be used across several controllers. For simplicity, we just embedded it in the controller.

Note how type-classes allow for separation of concerns. The model just defines the Repo type, without attaching any behavior. The Writes[Repo] type class just knows how to convert from a Repo instance to JSON, but knows nothing of the context in which it is used. Finally, the controller just knows how to create a JSON HTTP response.

Congratulations, you have just defined a web API that returns JSON! In the next section, we will learn how to fetch data from the GitHub web API to avoid constantly returning the same array.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.5.57