Documentation

§JSON と HTTP

Play は、JSON ライブラリと組み合わせて HTTP API を使用することによって、コンテンツタイプが JSON の HTTP リクエストとレスポンスをサポートしています。

コントローラ、アクション、ルーティングの詳細については、HTTP プログラミング を参照してください。

エンティティのリストを GET したり、新しいエンティティを作成する POST を受け入れる、簡単な RESTful Web サービスを設計することにより、必要な概念を説明します。このサービスでは、すべてのデータに JSON のコンテンツタイプを使用します。

このサービスで使用するモデルは次のとおりです。

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)
  }
}

§JSON によるエンティティのリストの提供

必要なインポートをコントローラに追加することから始めます。

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

object Application extends Controller {
  
}

Action を記述する前に、モデルを JsValue 表現に変換する仕組みが必要です。これには、暗黙の 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))

次に Action を記述します。

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

ActionPlace オブジェクトのリストを取得し、暗黙の Writes[Place] による Json.toJson を使って JsValue に変換し、これを結果のボディとして返します。Play は結果を JSON として認識し、適切な Content-Type ヘッダとレスポンス用のボディ値を設定します。

最後のステップとして、conf/routesAction 用のルートを追加します。

GET   /places               controllers.Application.listPlaces

ブラウザや HTTP ツールを使ってリクエストを行うことでアクションをテストできます。この例では unix コマンドラインツール cURL を使用しています。

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

処理結果は以下のようになります。

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}}]

§JSON による新しいエンティティインスタンスの作成

この Action では、JsValue をモデルに変換するために暗黙の Reads[Place] を定義する必要があります。

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 _)

次に 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.") ))  
    }
  )
}

この Action は、リストの場合より複雑です。 注意すべき事項を挙げます。

最後に conf/routes にルートバインディングを追加します。

POST  /places               controllers.Application.savePlace

成功とエラーのフローを検証するため、このアクションを有効なリクエストと無効なリクエストでテストします。

有効なデータによるアクションのテスト:

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

処理結果:

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

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

“name” フィールドが存在しない、無効なデータによるアクションのテスト:

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

処理結果:

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":[]}]}}

“lat” のデータ型が正しくない、無効なデータによるアクションのテスト:

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

処理結果:

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":[]}]}}

§まとめ

Play は JSON を用いた REST をサポートするように設計されており、これらのサービスの開発作業はすんなりと進むことでしょう。作業の大部分は、モデルの ReadsWrites を記述することになります。詳細については次の章で説明します。

Next: JSON Reads/Writes/Format コンビネータ


このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。