PlayFramework API用Jsonの出力

個人開発したアプリの宣伝
目的地が設定できる手帳のような使い心地のTODOアプリを公開しています。
Todo with Location

Todo with Location

  • Yoshiko Ichikawa
  • Productivity
  • Free

スポンサードリンク

PlayFrameworkでWebAPIとして動作するJsonを出力する。


基本形

OkメソッドにJsonValueオブジェクトを渡せば、'Content-Type: application/json'が吐かれる。

Ok( data:JsonValue )

以下、JsonValueの作成方法を紹介します。


文字列から生成

var jsonValue = Json.parse("""{"key":"value","number":3}""")
Ok( jsonValue )

出力

{"key":"value","number":3}


Listから生成

toJsonにListを渡せば、ジェネリクスより配列型のJsonValueが返される。

var jsonValue = Json.toJson( List("1", "2", "data") )
Ok( jsonValue )

出力

["1","2","data"]


Mapから生成

List同様、toJsonメソッドに渡せる。

var json = Json.toJson(Map("key" -> "data", "number" -> "3"))

出力

{"key":"data","number":"3"}


case classから生成

とはいえ、一般的な利用はユーザが定義したcase class等をJsonValueに生成する実装だろう。

ユーザ定義のclassに対しては、writerを定義する必要がある。

例えば、case classのフィールド名をJsonオブジェクトのキーと一致させる場合は、以下のようにしてwriterを定義できる。

case class mJsonData(name:String="data", number:Int=3)
implicit val writes: Writes[mJsonData] = Json.writes[mJsonData]
//Okメソッドに渡すのはJsonValue型 witerはtoJsonメソッドがimplicit引数として受け取ります
Ok( Json.toJson(mJsonData()) )

出力

{"name":"data","number":3}
No apply function found for controllers.Controller.mJsonData

のようなエラーが出る場合は、case classがimport出来てない可能性がある。Okメソッドが存在するContoller.thisのスコープで定義すれば(メソッド内のスコープではないので注意)正常に動作する場合がある。


Jsonフィールドのマッピングをカスタム定義にする

Jsonフィールドとclassフィールド名を一致させない場合は以下のようにwriterを定義する。

case class mJsonData(name:String="data", lat:Double=39.1234, lon:Double=140.4321)
implicit val mWrites: Writes[mJsonData] =
  new Writes[mJsonData] {
    def writes(res: mJsonData) =
      Json.obj("address" -> res.name, "latitude" -> res.lat, "longitude " -> res.lon)
  }

出力

{"address":"data","latitude":39.1234,"longitude ":140.4321}


また以下のように、lat,lonをpositionとして纏めるような定義もできる。

implicit val mWrites: Writes[mJsonData] =
  new Writes[mJsonData] {
    def writes(res: mJsonData) =
      Json.obj("address" -> res.name, "position" -> List(res.lon, res.lat))
  }
{"address":"data","position":[140.4321,39.1234]}

配列は以下のように定義することもできる。

implicit val mWrites: Writes[mJsonData] =
  new Writes[mJsonData] {
    def writes(res: mJsonData) =
      Json.obj("address" -> res.name, "position" -> Json.arr(res.lon, res.lat))
  }


オブジェクトの入れ子を定義する

オブジェクトが入れ子になっている場合も要領は同じで、オブジェクト単位でwriterを用意して上げれば良い。

以下のようなcase classの場合、

case class InnerData(innerKey:String="innerData", innerValue:String="foo")
case class mJsonData(name:String="data", lat:Double=39.1234, lon:Double=140.4321, extension:InnerData=InnerData())

このようなwriterを用意することで、

implicit val innerWrites: Writes[InnerData] = Json.writes[InnerData]
implicit val mWrites: Writes[mJsonData] =
  new Writes[mJsonData] {
    def writes(res: mJsonData) =
      Json.obj("address" -> res.name, "position" -> Json.arr(res.lon, res.lat), "inner" -> res.extension )
  }
Ok( Json.toJson(mJsonData()) )

出力

{"address":"data","position":[140.4321,39.1234],"inner":{"innerKey":"innerData","innerValue":"foo"}}

のようなJsonが出力できる。


JSから扱うAPIの場合

JSからCross Domain, Cross Originでレスポンスデータを参照する場合、Access-Control-Allow-Originヘッダーが必要になる。

OkメソッドにwithHeadersを追記する。例では*(全てのホストからOK)としているが、用途によってホスト名を記述すればよい。

withHeaders("Access-Control-Allow-Origin" -> " *")

尚、Access-Control-Allow-OriginはCSRFの対策とはならないのでプリフライトリクエストを用いたリクエスト検証は後日記述する。