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の対策とはならないのでプリフライトリクエストを用いたリクエスト検証は後日記述する。