前回のエントリで、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
- メディア: ペーパーバック
- この商品を含むブログを見る