Scalatra2.2+scalatra-jsonでJSONを処理する
scalatra-json
Scalatraでは、JSONライブラリとしてlift-jsonをベースとしたscalatra-lift-jsonが提供されているので、JSONを利用する際はそれを使っていました。しかし、Scalatra 2.2系(現在まだRCです)からscalatra-lift-jsonの提供をやめ、json4sをベースとしてscalatra-jsonという名前でJSONライブラリを提供するようです。
json4sは、Scala(もしくはJava)の各種JSONライブラリを同じように扱うこと(正確には、JSON処理のための単一のASTを提供すること)を目的としたライブラリです。ちょうど、ロギングライブラリのslf4jがlogbackやlog4jのファサードとなっているのと同じです。ScalaのJSONライブラリの中では使いやすい、lift-jsonに近い形でJSONが扱えるようになっているようです。実際に利用するパーサーは、今のところjson4sのネイティブパーサーとJacksonに対応しています。
scalatra-lift-jsonからscalatra-jsonに乗せかえてみましたので使い方をメモします。
開発環境
今回の開発環境は以下の通りです。
- OSX Mountain Lion
- Scala 2.9.2
- sbt 0.12.1
- giter8 0.4.5
Scalatraプロジェクトの作成
まずはScalatraプロジェクトを作ります。sbt+giter8を使うのが楽です。giter8はScalaアプリケーションのひな形を作成するためのツールです。giter8がインストールされていない場合は、Homebrewからインストールします。
$ brew install giter8
giter8でScalatraプロジェクトのテンプレートを作成します。
$ g8 scalatra/scalatra-sbt
今回のプロジェクトの設定は以下のようにしました。
organization [com.example]: jp.classmethod package [com.example.myapp]: jp.classmethod.scalatrajson name [My Scalatra Web App]: scalatra-json servlet_name [MyServlet]: JsonTestServlet version [0.1.0-SNAPSHOT]:
作成したプロジェクトのディレクトリに移動して、sbtを起動します。
$ cd scalatra-json/ $ sbt
sbtが起動してreloadとupdateが済んだら、以下のコマンドでJettyを起動します。
> container:start
localhost:8080にアクセスしてウェルカムページが表示されれば、Scalatraプロジェクトの作成は成功です。
ビルド依存性の修正
scalatra-jsonが使えるように、build.sbtを以下の通りに修正します。
build.sbt
organization := "jp.classmethod" name := "scalatra-json" version := "0.1.0-SNAPSHOT" scalaVersion := "2.9.2" seq(webSettings :_*) classpathTypes ~= (_ + "orbit") libraryDependencies ++= Seq( "org.scalatra" % "scalatra" % "2.2.0-SNAPSHOT", "org.scalatra" % "scalatra-scalate" % "2.2.0-SNAPSHOT", "org.scalatra" % "scalatra-specs2" % "2.2.0-SNAPSHOT" % "test", "org.scalatra" % "scalatra-json" % "2.2.0-SNAPSHOT", // "org.json4s" %% "json4s-native" % "3.1.0", // ネイティブパーサーを利用する場合はこっち "org.json4s" %% "json4s-jackson" % "3.1.0", "ch.qos.logback" % "logback-classic" % "1.0.6" % "runtime", "org.eclipse.jetty" % "jetty-webapp" % "8.1.7.v20120910" % "container;test", "org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts (Artifact("javax.servlet", "jar", "jar")) ) resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
Scalatra2.2系はまだRCですので、SNAPSHOTを使います。また、今回はScala 2.9.2を使いますが、Scala 2.10系で動かす場合は以下のようにバイナリバージョンを付加するようsbtに指示する必要があります。
"org.scalatra" % "scalatra" % "2.2.0-SNAPSHOT" cross CrossVersion.binary,
サーブレットの修正
以下のエントリでscalatra-lift-jsonの使い方が紹介されていたので、サーブレットの実装を真似させてもらいました。
Scalatra+scalatra-lift-jsonでJSON
JSONパーサーはJacksonを使いました。
JsonTestServlet.scala
package jp.classmethod.scalatrajson import org.scalatra._ import scalate.ScalateSupport import org.json4s._ import org.scalatra.json._ class JsonTestServlet extends ScalatraServlet with ScalateSupport with JacksonJsonSupport { protected implicit val jsonFormats: Formats = DefaultFormats get("/") { scaml("index") } get("/json") { Extraction.decompose( Person(1, "ぱみゅぱみゅ", 19) ) } post("/json") { parsedBody match { case JNothing => halt(400, "invalid json") case json: JObject => { val pamyu: Person = json.extract[Person] pamyu.name } case _ => halt(400, "unknown json") } } notFound { // remove content type in case it was set through an action contentType = null // Try to render a ScalateTemplate if no route matched findTemplate(requestPath) map { path => contentType = "text/html" layoutTemplate(path) } orElse serveStaticResource() getOrElse resourceNotFound() } } case class Person(id: Long, name: String, age: Int)
scalatra-lift-jsonを利用した場合との違いは、以下の3点だけです。
json4sとscalatra-jsonのモジュールをインポート
scalatra-lift-jsonで使っていたlift-jsonモジュールのインポートの代わりに、scalatra-jsonのモジュールをインポートします。
scalatra-lift-json
import liftjson.LiftJsonSupport import net.liftweb.json._
scalatra-json
import org.json4s._ import org.scalatra.json._
ミックスインするJSONヘルパートレイトを変更
JSONヘルパートレイトをscalatra-lift-jsonで提供されるlift-jsonのものから、scalatra-jsonで提供されるJacksonもしくはネイティブのものに変更します。
scalatra-lift-json
class JsonTestServlet extends ScalatraServlet with ScalateSupport with LiftJsonSupport
scalatra-json
// Jackson class JsonTestServlet extends ScalatraServlet with ScalateSupport with JacksonJsonSupport // or // ネイティブ class JsonTestServlet extends ScalatraServlet with ScalateSupport with NativeJsonSupport
Formatsの暗黙のパラメータを宣言
JacksonJsonSupport(NativeJsonSupport)がミックスインしているJValueResultトレイトの、implicitな抽象メソッドであるjsonFormatsが実装されていません。valで宣言して実装します。
protected implicit val jsonFormats: Formats = DefaultFormats
後はscalatra-lift-jsonと全く同じ構文で利用できます。ケースクラスへの抽出もそのまま使えるので、JSON-Objectマッピングのための面倒なコードを書く必要がないのが嬉しいです。
まとめ
最初にこの変更をリリースノートで見た際は、lift-jsonから変える必要があったのかなと思いました。しかし、json4sのドキュメントに書いてありましたが、lift-jsonはScalaフレームワークであるLiftのJSONユーティリティとして提供されていることもあり、新しいバージョンのScalaへの対応はLift本体のリリーススケジュールにどうしても引っ張られてしまいます。Liftは使ったことがないのであまりよく知りませんが、大きめのフレームワークということもあってか、新しいバージョンのScalaへの対応があまり早くないようです。
Scalatraでも、lift-jsonに依存して新しいバージョンのScalaにJSONライブラリが対応できないといったことを避けるため、今回の変更をしたものと思われます。json4sは既にScala 2.10に対応したモジュールがMavenリポジトリに公開されているようですので、JSONを扱うScalatraプロジェクトもScalatra 2.2系を使えばScala 2.10に乗せかえることができます。
余談ですが、Scalatra2.2系からサーブレットを登録するLifeCycle継承クラスの名前が、ScalatraからScalatraBootstrapに変更されたようです。名前がScalatraのままだとサーバー起動時に警告が出ますので、JSONライブラリの乗せかえのついでにクラス名を変更しておきましょう。