JSON with HTTP

Play框架JSON交互
本文介绍如何使用Play框架处理JSON格式的数据交互,包括获取实体列表和创建新实体的过程。通过定义模型类、实现读写转换器,并结合HTTP API完成RESTful服务的搭建。

JSON with HTTP

Play supports HTTP requests and responses with a content type of JSON by using the HTTP API in combination with the JSON library.

See HTTP Programming for details on Controllers, Actions, and routing.

We’ll demonstrate the necessary concepts by designing a simple RESTful web service to GET a list of entities and accept POSTs to create new entities. The service will use a content type of JSON for all data.

Here’s the model we’ll use for our service:

case class Location(lat: Double, long: Double)

case class Place(name: String, location: Location)

object Place {
    
  var list: List[Place] = {
    List(
      Place(
        "Sandleford",
        Location(51.377797, -1.318965)
      ),
      Place(
        "Watership Down",
        Location(51.235685, -1.309197)
      )
    )
  }
    
  def save(place: Place) = {
    list = list ::: List(place)
  }
}

Serving a list of entities in JSON

We’ll start by adding the necessary imports to our controller.

import play.api.mvc._
import play.api.libs.json._
import play.api.libs.functional.syntax._

object Application extends Controller {
  
}

Before we write our Action, we’ll need the plumbing for doing conversion from our model to a JsValue representation. This is accomplished by defining an implicit Writes[Place].

implicit val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
  (JsPath \ "long").write[Double]
)(unlift(Location.unapply))

implicit val placeWrites: Writes[Place] = (
  (JsPath \ "name").write[String] and
  (JsPath \ "location").write[Location]
)(unlift(Place.unapply))

Next we write our Action:

def listPlaces = Action {
  val json = Json.toJson(Place.list)
  Ok(json)
}

The Action retrieves a list of Place objects, converts them to a JsValue using Json.toJson with our implicitWrites[Place], and returns this as the body of the result. Play will recognize the result as JSON and set the appropriate Content-Type header and body value for the response.

The last step is to add a route for our Action in conf/routes:

GET   /places               controllers.Application.listPlaces

We can test the action by making a request with a browser or HTTP tool. This example uses the unix command line tool cURL.

curl --include http://localhost:9000/places

Response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 141

[{"name":"Sandleford","location":{"lat":51.377797,"long":-1.318965}},{"name":"Watership Down","location":{"lat":51.235685,"long":-1.309197}}]

Creating a new entity instance in JSON

For this Action we’ll need to define an implicit Reads[Place] to convert a JsValue to our model.

implicit val locationReads: Reads[Location] = (
  (JsPath \ "lat").read[Double] and
  (JsPath \ "long").read[Double]
)(Location.apply _)

implicit val placeReads: Reads[Place] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "location").read[Location]
)(Place.apply _)

Next we’ll define the Action.

def savePlace = Action(BodyParsers.parse.json) { request =>
  val placeResult = request.body.validate[Place]
  placeResult.fold(
    errors => {
      BadRequest(Json.obj("status" ->"KO", "message" -> JsError.toFlatJson(errors)))
    },
    place => { 
      Place.save(place)
      Ok(Json.obj("status" ->"OK", "message" -> ("Place '"+place.name+"' saved.") ))  
    }
  )
}

This Action is more complicated than our list case. Some things to note:

  • This Action expects a request with a Content-Type header of text/json or application/json and a body containing a JSON representation of the entity to create.
  • It uses a JSON specific BodyParser will parse the request and provide request.body as a JsValue.
  • We used the validate method for conversion which will rely on our implicit `Reads[Place]’.
  • To process the validation result, we used a fold with error and success flows. This pattern may be familiar as it is also used for form submission.
  • The Action also sends JSON responses.

Finally we’ll add a route binding in conf/routes:

POST  /places               controllers.Application.savePlace

We’ll test this action with valid and invalid requests to verify our success and error flows.

Testing the action with a valid data:

curl --include
  --request POST
  --header "Content-type: application/json" 
  --data '{"name":"Nuthanger Farm","location":{"lat" : 51.244031,"long" : -1.263224}}' 
  http://localhost:9000/places

Response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 57

{"status":"OK","message":"Place 'Nuthanger Farm' saved."}

Testing the action with a invalid data, missing “name” field:

curl --include
  --request POST
  --header "Content-type: application/json"
  --data '{"location":{"lat" : 51.244031,"long" : -1.263224}}' 
  http://localhost:9000/places

Response:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 79

{"status":"KO","message":{"obj.name":[{"msg":"error.path.missing","args":[]}]}}

Testing the action with a invalid data, wrong data type for “lat”:

curl --include
  --request POST
  --header "Content-type: application/json" 
  --data '{"name":"Nuthanger Farm","location":{"lat" : "xxx","long" : -1.263224}}' 
  http://localhost:9000/places

Response:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 92

{"status":"KO","message":{"obj.location.lat":[{"msg":"error.expected.jsnumber","args":[]}]}}

Summary

Play is designed to support REST with JSON and developing these services should hopefully be straightforward. The bulk of the work is in writing Reads and Writes for your model, which is covered in detail in the next section.

Next: JSON Reads/Writes/Formats Combinators


Found an error in this documentation? The source code for this page can be found here. After reading thedocumentation guidelines, please feel free to contribute a pull request.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值