Play framework 2.0 betaが出たようです8 – JSONでデータのやりとりをする

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

JSONフォーマットでのやり取り

Play framework 1.2系では標準でJSON形式データのやりとりが可能です。
play-scalaを利用した際にも、Gsonなどのライブラリと組みあわせてJSON形式のデータをやりとりすることができました。
Play framework 2.0(Scala)では標準でJSONデータを扱うための機能が組み込まれているので、試してみましょう。

今回使用した動作環境は以下のとおりです。

  • OS : MacOS X 10.7.2
  • Playframework : HEAD
  • Java : 1.6.0_26
  • Eclipse : 3.7

注意:
この記事ではgithubから取得したHEAD版を使用して試しています。
正式リリース前の状況のため思わぬ不具合が生じるかもしれませんので、ご了承ください。

ソースのHEADを取得してセットアップ

まだgithubからソースを取得していない人は、play2.0のソースを取得してビルドしましょう。

$ git clone https://github.com/playframework/Play20.git
$ cd Play20/framework
$ ./build
> complile
> build-repository
> publish-local

すでにソースを取得している人は、最新の状態に更新してビルドします。

$ git pull
$ cd Play20/framework
$ ./build
> compile
> build-repository
> publish-local

これで準備ができました。

プロジェクト作成

Scalaベースのプロジェクトを作成し、sbtコンソールを起動します。

% play new json
% cd json
% play

eclipsifyコマンドが使えるようになったので、eclipsifyを実行してeclipseプロジェクト化してインポートしてソースの修正をしましょう。

> $ eclipsify

シンプルなJSONデータを返す

まずは単純な文字列を返します。Apllicationコントローラーで、play.api.libs.jsonパッケージをimportし、toJsonに文字列を渡してみてください。

//Application
import play.api.libs.json._

def index = Action {
  val json = "hello"
  Ok(toJson(json))
}

アプリケーションを起動させてlocalhost:9000にアクセスすると、ブラウザには下記のように表示されるはずです。

"hello"

コレクションをJSON形式で返す

リストも渡すことができます。

//Application
def index = Action {
  val jsonList = List("apple", "orange", "banana")
  Ok(toJson(jsonList))
}

リストは下記形式で返ってきます。

["apple","orange","banana"]

Mapの場合は下記のように、キーに対応する値の型に応じたWritesクラスの変換用メソッドを指定します。
Mapの値もString型にしているので、StringWritesを指定しています。
2012/1/3以降のHEADであればMapをそのまま渡すだけで使用できます。
Application.scalaとroutesを下記のように記述してみてください。

//Application
def jsonMap = Action {
  val m = Map("key1" -> "val1","key2" -> "val2")
  //Ok(toJson(m)(Writes.StringWrites))
  Ok(toJson(m))
}
#routes
GET     /jsonMap                 	controllers.Application.jsonMap()

localhost:9000/jsonMapにアクセスすると、下記のように表示されます。

{"key1":"val1","key2":"val2"}

任意の型をJSON形式でやりとりする

任意のオブジェクトをシリアライズすることも可能です。その場合、シリアライズとデシリアライズするためのルールを定義します。
まずはmodelsパッケージにcaseクラスを用意しましょう。

package models

case class PostData(msg: String, date: String)

msgとdateプロパティを持つだけのシンプルなcase classです。
そしてそのクラスの変換用プロトコルを定義します。変換用プロトコルはplay.api.libs.json.Formatを継承して作成します。

package models

import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.json.Writes._

case class PostData(msg: String, date: String)

trait Protocol {
  implicit object PostDataFormat extends Format[PostData] {
    def reads(json: JsValue): PostData = PostData(
      (json \ "msg").as[String],
      (json \ "date").as[String])

    def writes(p: PostData): JsValue = JsObject(List(
      "msg" -> JsString(p.msg)))
  }
}

readsメソッドはJSON形式の文字列をオブジェクトに変換し、writesメソッドはオブジェクトをJSON形式へ変換します。
Applicationコントローラーにメソッドを追加して試してみましょう。

package controllers

import play.api._
import play.api.libs.json._
import play.api.mvc._
import models.PostData
import models.Protocol

object Application extends Controller with Protocol {

・・・・・・・・・・・

  def jsonReturn = Action {
    val obj = PostData("jsonReturn", "2011-12-31")
    Ok(toJson(obj))
  }

  def jsonParse = Action {
    val postJson = """{"msg": "jsonParse", "date": "2012-01-01"}"""
    val result = Json.parse(postJson).as[PostData]
    println("result = %s".format(result))
    Ok("ok")
  }
}
#routes
GET     /jsonReturn                 controllers.Application.jsonReturn()
GET     /jsonMap                 	controllers.Application.jsonMap()

Protocolトレイトにimplicit objectとしてPostDataオブジェクトの変換プロトコルが定義されているので、importすればtoJsonに暗黙的に渡されます。
また、Json.parse(<文字列形式のJSONデータ>).as[<変換したいオブジェクト>]とすれば、readメソッドに応じてJSON形式の文字列がパースされます。
localhost:9000/jsonReturnにアクセスしてみてください。下記のように表示されるはずです。

{"msg":"jsonReturn"}

まとめ

今回はPlay framework 2.0(Scala)でのJSON形式データのやり取り方法についてご紹介しました。
単純なオブジェクトでもわざわざ変換ルールの定義したり面倒だなぁと思いますが、JSON周りは最近かなり機能が追加されているので、
オブジェクト用のデフォルト変換プロトコルが追加される等、リリースまでにはまだ変わるかもしれません。
さらに詳細な説明はPlay! framework Advent Calendar 2011の7日目 ※1 にありますので、確認してみてください。

オブジェクト変換についての追記

直近のコミットで、オブジェクトのシリアライズについて追加がありました。
Format型の変数を用意するところは同じですが、productFormat関数を書けば
readやwrite関数を記述しなくても、プロパティ数に応じたシリアライズ処理を行なってくれるようです。

package controllers

import play.api._
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.json.Generic._

object Application extends Controller {
  
  def index = Action {
	implicit val UserFormat:Format[User] = productFormat2("name", "msg")(User.apply _)(User.unapply(_))
	val obj = User("taro","hello")
	Ok(toJson(obj))
  }
  
}

case class User(name:String,msg:String)

参考サイトなど