注意,这一节的内容最早由 Pascal Voitot 发表在 mandubian.com 上。(文章太旧,请带着批判的眼光去读。)
这个特性还处于实验中,因为 Scala 宏在 Scala 2.10.0 中仍是实验性的。如果你不想使用 Scala 中的实验性特性,请手写 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)
你为这个样例类写了 5 行代码,你知道吗,许多人认为为他们的类写一个 Reads[TheirClass]
是非常不 cool 的,因为像 Java 的 JSON 框架,如 Jackson 或 Gson,会为你做这些事情,而你根本不需要写这些多余的代码。
我们会这么说 Play2.1 的 JSON 序列化与反序列化:
但对于一些人来说,以上的好处并无法抹平额外代码带来的麻烦。
我们相信这是一个非常好的方法,因此坚持并提出:
虽然被加强了,但仍然没改变要写额外代码的事实。
鉴于我们是完美主义者,我们提出了一种新的方法来达到同样的效果:
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]
只需要一行!你马上可能会问:
它有使用运行时字节码增强吗? -> 没有 它有使用运行时自省机制吗? -> 没有 它会打破类型安全吗? -> 不会
所以呢?
在创造了 JSON coast-to-coast 设计一词后,让我们把它叫做:JSON Inception。
正如之前所解释的:
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)
下面是描述 inception 概念的等式:
(Case Class INSPECTION) + (Code INJECTION) + (COMPILE Time) = INCEPTION
也许你自己就可以推断出来,为了达到前面说的代码等价性,我们需要:
Person
样例类name
,age
和 lovesChocolate
3 个字段以及它们的类型Person.apply
我要立马阻止你,并不是你想的那样
代码注入并不是依赖注入。不是 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)
不多也不少。
没错,一切都是在编译期执行的。并没有运行时字节码增强,也没有运行时自省。
由于一切都是在编译期解析的,因此如果没有导入各个字段类型所需的隐式转换,就会报编译错误。
我们需要启用 Scala 的一个特性来支持 Json inception:
以上这些可以由 Scala 2.10 中的一个新的实验性特性来提供:Scala 宏。
Scala 宏是一个拥有具大潜力的新特性(仍是实验性的)。有了它,可以:
请注意:
你可能发现了,写宏并不是一个简单的过程,因为你写的宏是在编译期执行的。
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…
这也是为什么我把它叫做 Inception。
因此想完全按照你想做的来,来需要一些练习。提供的 API 也相当复杂,文档也不齐全。因此,当你开始使用宏时,你需要有坚持不懈的精神。
请注意,JSON inception 只能用于含有
unapply/apply
方法的结构。
自然地,你可将它用于 Writes[T]
和 Format[T]
。
import play.api.libs.json._
implicit val personWrites = Json.writes[Person]
import play.api.libs.json._
implicit val personWrites = Json.format[Person]
这样当你操作你的类的一个实例,隐式的 Reads/Writes 就会被自动推断出来。
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]
}