Documentation

You are viewing the documentation for the 2.6.x development release. The latest stable release series is 2.4.x.

§マクロによる JSON インセプション

このドキュメントは、当初 Pascal Voitot (@mandubian) の記事 mandubian.com として公開されたものです

この機能はまだ実験的です。というのも Scala のマクロが 2.10 においてまだ実験的だからです。Scala の実験的機能を使用したくなければ、 手書きの Reads/Writes/Format を使用して厳密に対応させてください。

§ケースクラスのためにデフォルトの Reads/Writes/Format を書くのは面倒くさい!

ケースクラスのための Reads[T] を書く方法を思い出してみましょう。

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class Person(name: String, age: Int, lovesChocolate: Boolean)

implicit val personReads = (
  (__ \ 'name).read[String] and
  (__ \ 'age).read[Int] and
  (__ \ 'lovesChocolate).read[Boolean]
)(Person)

ひとつのケースクラスのために、4 行のコードを書きました。
あなたはどう思いますか?
人によっては Reads[TheirClass] を書くというのは格好良くない、と考えるようです。その理由は、Jackson や Gson のような Java の JSON フレームワークが通常そのようなコーディングを全く必要とせずに、カーテンの裏側でよしなに計らってくれるから、というものです。実際、そのように考えている人々からの改善要望も聞いていました。
そして、私たちは議論の結果、 Play 2.1 の JSON シリアライザ/デシリアライザを以下のようなものにしました。

しかし、一部の人にとっては、これら利点はケースクラスそれぞれについてのコード量増加を正当化するほどではありませんでした。

一方で私達はこのアプローチ自体は正しいと信じているので、追加で次のものを提案しました。

これらの追加によって、コード量の増加を抑えつつ、機能的には先ほどの「4 行の追加コード」と同じものを実現します。

§ミニマリストになろう

私達は完璧主義者として、先ほどのコードの新しい記述方法を提案します。

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class Person(name: String, age: Int, lovesChocolate: Boolean)

implicit val personReads = Json.reads[Person]

たったの 1 行です。
今、こんな疑問が浮かんだのではないかと思います。

実行時のバイトコード書き換えをしている? -> いいえ

実行時の型検査をしている? -> いいえ

型安全を破壊している? -> いいえ

どういうこと?

勝手に作った 端から端への JSON 設計 というバズワードに倣って、これを JSON インセプション と呼ぶことにしましょう。



§JSON インセプション

§等価なコードについて

先ほどご説明したとおり、以下のコードと等価です。

import play.api.libs.json._
// please note we don't import functional.syntax._ as it is managed by the macro itself

implicit val personReads = Json.reads[Person]

// IS STRICTLY EQUIVALENT TO writing

implicit val personReads = (
  (__ \ 'name).read[String] and
  (__ \ 'age).read[Int] and
  (__ \ 'lovesChocolate).read[Boolean]
)(Person)

§インセプションの方程式

これが颯爽と現れた インセプション の概念を説明するための方程式です。

(Case Class INSPECTION) + (Code INJECTION) + (COMPILE Time) = INCEPTION

§ケースクラス・インスペクション

想像がつくかもしれませんが、先ほどのコード等価性を確保するためには、次のものが必要です。


§インジェクションとは?

先に断っておきますが…

コードインジェクションとは、 DI のことではありません…
インセプションの裏に Spring はいません。IOC や DI はいるか、って?…いやいやいや ;)

私は、一般に「インジェクション」という言葉からは IOC や Spring がすぐ連想される、ということを理解しています。しかし、この用語の本来の意味を改めて確立しなおしたいと考えて、あえてこの用語を使います。ここでのコードインジェクションの意味は、 「コンパイル時に、コンパイル結果としての Scala の AST (Abstract Syntax Tree/抽象構文木) の中に、コードをインジェクトする」 です。

このインジェクションの結果、繰り返しになりますが、 Json.reads[Person] はコンパイルされ、コンパイル結果の AST の中で以下のコードに置換されます。

(
  (__ \ 'name).read[String] and
  (__ \ 'age).read[Int] and
  (__ \ 'lovesChocolate).read[Boolean]
)(Person)

これ以上でも、これ以下でもありません…


そう、すべてはコンパイル時に行われます。
実行時のバイトコードの書き換えはありません。
実行時の型検査もありません。

全てがコンパイル時に解決されるため、全フィールドの全ての型に要求される implicit 値が import されていない場合、コンパイルエラーが発生します。


§JSON インセプションは Scala 2.10 のマクロです

私達には以下の 3 つを実現できる Scala の機能が必要でした。

結果的には、JSON インセプションは Scala 2.10 で新たに導入された試験的な機能 Scala マクロ によって実装されました。

Scala マクロは大きなポテンシャルを持った (まだ試験的な) 新機能です。これにより以下のことができます。

また、補足として以下のことを知っておいてください。

お気づきかもしれませんが、マクロを書くというのは並大抵の仕事ではありません。マクロのコードがコンパイラーのランタイムに (または、 universe の中で) 実行されるからです。

So you write macro code
  that is compiled and executed 
  in a runtime that manipulates your code 
     to be compiled and executed 
     in a future runtime…

このこともまた私がこれを*インセプション*と呼ぶ理由の一つです ;)

したがって、この仕事をやり遂げるには少し頭の体操が必要です。また、 API はかなり複雑で、ドキュメントも完全ではありません。そのため、マクロを使いはじめるにあたっては相応の努力をすることになります。

Scala マクロについてはまだまだ説明したいことが沢山あるので、きっと別の記事も書くと思います。
この記事には、 Scalaマクロの正しい使い方について熟考するきっかけになって欲しい、という想いも込められています。
強力な力にはより大きな責任が伴いますから、これから一緒に話し合いを重ねて、良い作法を少しずつ確立していければ何よりです。


§Writes[T] と Format[T]

JSON インセプションは、unapply/apply 関数の入力/出力の型が互いに対応している場合にのみ機能する、ということに気をつけてください。

もちろん、Writes[T]Format[T]インセプト することもできます。

§Writes[T]

import play.api.libs.json._
 
implicit val personWrites = Json.writes[Person]

§Format[T]

import play.api.libs.json._

implicit val personWrites = Json.format[Person]

§特別なパターン

import play.api.libs.json._

case class Person(name: String, age: Int)

object Person{
  implicit val personFmt = Json.format[Person]
}
import play.api.libs.json._

case class Person(names: List[String])

object Person{
  implicit val personFmt = Json.format[Person]
}

§既知の制限

Next: XML を使う


このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。