前回のエントリで、Scalatraがレスポンスを返す仕組みについて紹介しました。Any型
で返すのは型安全的にどうなの?という気持ちも有り、いつの日か変えたいという気持ちですが(Scalatra 3.0?)、現状はそんな仕組みになっています。
Scalatra-JSON
前回説明した通り、ScalatraではAny型
で受けたコードブロックの返り値を、パターンマッチで振り分けます。
NativeJsonSupport trait
か、JacksonJsonSupport trait
をmix-inすると、下記のrenderPipeline
メソッドが有効になります。
Json4sがXMLをサポートしている関係で大量のXML関係のコードが含まれていますが、飛ばしながら読み進めていきましょう。
JValueResult trait
が持つrenderPipeline
メソッドがもっとも優先的に起動されます。対応するcaseが無ければ、orElse super.renderPipeline
により次のrenderPipeline
へ処理が委譲されます。ここでのsuper
が指す先はJsonOutput trait
のrenderPipeline
メソッドになり、更にそのsuper
が指す先はScalatraBase trait
のrenderPipeline
メソッドになります。
JValueResult trait
が持つrenderPipeline
メソッドの一部を以下に引用します。Json4sのASTであるJValue
型であれば、すぐにJsonOutput trait
のrenderPipeline
へ移譲されていることが分かるでしょう。また、詳しくは後述しますが、case a: Any if isJValueResponse && customSerializer.isDefinedAt(a)
によりJson ASTへ変換可能なcase classがJValue型
として処理されていることが分かるでしょう。
override protected def renderPipeline: RenderPipeline = renderToJson orElse super.renderPipeline private[this] def renderToJson: RenderPipeline = { case JNothing => case JNull => response.writer.write("null") case a: JValue => super.renderPipeline(a) case a: Any if isJValueResponse && customSerializer.isDefinedAt(a) => customSerializer.lift(a) match { case Some(jv: JValue) => jv case None => super.renderPipeline(a) } case status: Int => super.renderPipeline(status)
また、status: Int
のように一旦JValueResult trait
のrenderPipeline
で処理されるけど、すぐにsuper
を呼び出しているパターンが有ることも分かるでしょう。
JsonOutput trait
のrenderPipeline
は以下のようなコードになっていて、JSONPへ対応するコードが混じっていて分かりづらいですが、通常のJSONであればwriteJson(transformResponseBody(jv), writer)
というコードでwriterに渡されていることが分かるでしょう。
override protected def renderPipeline = ({ case JsonResult(jv) => jv case jv: JValue if format == "xml" => contentType = formats("xml") writeJsonAsXml(transformResponseBody(jv), response.writer) case jv: JValue => // JSON is always UTF-8 response.characterEncoding = Some(Codec.UTF8.name) val writer = response.writer val jsonpCallback = for { paramName <- jsonpCallbackParameterNames callback <- params.get(paramName) } yield callback jsonpCallback match { case some :: _ => // JSONP is not JSON, but JavaScript. contentType = formats("js") // Status must always be 200 on JSONP, since it's loaded in a <script> tag. status = 200 if (rosettaFlashGuard) writer.write("/**/") writer.write("%s(%s);".format(some, compact(render(transformResponseBody(jv))))) case _ => contentType = formats("json") if (jsonVulnerabilityGuard) writer.write(VulnerabilityPrelude) writeJson(transformResponseBody(jv), writer) () } }: RenderPipeline) orElse super.renderPipeline
このように、Scalatraではコードブロックが返す値の型に応じて色々な処理へ分岐していること、新しい処理をカスケードして追加できることが分かってもらえたでしょうか。
case classへの対応
既にコードは出てきましたが、ScalatraではAnyで受けた値を、パターンマッチにより処理を振り分けています。また、Json4sにはcase classへのマッピング機能があります。そのため、Json4sの機能を使って、Json ASTへ変換可能なcase classを受け取った場合は、自動的にJson ASTへ変換して処理したいわけです(わざわざ呼び出し側でJson ASTに変換してから呼び出すのもアレなので)。
Json4sにはCustomSerializer
という機能が用意されていて、これによりターゲットとしているオブジェクトがJson ASTに変換可能であるか判定することができます。
これは他のJsonモジュールにはない、Json4s特有の機能です。この機能があるため、Anyで返したオブジェクトがJson ASTに変換可能か、判断することができるようなっています。
- 作者: Dave Hrycyszyn,Stefan Ollinger,Ross A. Baker
- 出版社/メーカー: Manning Publications
- 発売日: 2016/05/23
- メディア: ペーパーバック
- この商品を含むブログを見る