我试图理解这里解释的OWritesOps类的每一个实现:http://kailuowang.blogspot.mx/2013/11/addremove-fields-to-plays-default-case.html
scala / play doc的结构方式并没有什么帮助(在这方面Java(doc)胜过Scala(doc)。)
让我们从:addField:
开始def addField[T: Writes](fieldName: String, field: A => T): OWrites[A] =
(writes ~ (__ \ fieldName).write[T])((a: A) => (a, field(a)))
我只能猜测~
相当于"和" (我从这里收集它:http://mandubian.com/2012/10/01/unveiling-play-2-dot-1-json-api-part2-writes-format-combinators/)
好吧,让我们这样做吧:(writes ~ (__ \ fieldName).write[T])
创建Writes
(组合)的新实例。
(__ \ fieldName)
怎么样?经过大量挖掘Play Doc之后,我得出了一个(摇摇欲坠的)结论,它产生了一个JsPath实例?我是如何得出这个结论的?它是.write[T]
之后的结果; JsPath
有这种方法。
旁边,我在某处读到__
是JsPath
的快捷方式(我忘了在哪里,我猜它也在Play Doc中,但是使用Scala doc it' s几乎不可能找到回到那个页面的路。)
好的......,(writes ~ (__ \ fieldName).write[T])
生成OWrites
的实例。下一步:这是什么?:((a: A) => (a, field(a)))
哦......,将参数应用到" apply" OWrites的方法;它需要一个带有以下签名((A) => JsObject)
好的....所以.... (a, field(a))
应该创建JsObject
的实例。所以,我打开了播放文档JsObject
。嗯...,我猜它是这个构造函数(?):JsObject(fields: Seq[(String, JsValue)])
....但是,......我真的怀疑它......我迷失在这里。
无论如何,让我们看一个如何使用它的例子:
val customWrites: Writes[Person] = Json.Writes[Person]. addField("isAdult", _.isAdult)
所以...,_.isAdult
应该映射到具有以下签名的块:A => T
...但是......,如果真的如此,那我就是期待{x => x.isAdult}
....所以我猜_.isAdult
是Scala(?)提供的语法糖。
所以...回到addField
的定义:field(a)
执行块" _。isAdult"。我可以在此推断a
是Person
的实例。
你能帮我解读这一切吗?
Scala很酷,但它的doc系统(仍然)很糟糕:(
答案 0 :(得分:3)
~
是在OWrites
实例上调用的方法的名称。 writes ~ (__ \ fieldName).write[T])
与writes.~((__ \ fieldName).write[T]))
相同。 注意 ~
参数周围的括号。
这个等价形式有两件事:
在Scala中,任何带有一个参数的方法都可以使用中缀表示法编写。例如,如果您有一个实例list: java.util.ArrayList[Int]
,则可以编写list add 5
。
虽然方法名称不必是字母数字(如Java中所示),但并非所有运算符都具有相同的优先级。例如,1 + 5 * 2
转换为1.+(5.*(2))
而不转换为1.+(5).*(2)
。 Here's a summary of operator precedence in Scala。
但是,OWrites
在其界面中没有名为~
的方法。如果您导入play.api.libs.functional.syntax._
,则会进行隐式转化
implicit def toFunctionalBuilderOps[M[_], A](a: M[A])(implicit fcb: FunctionalCanBuild[M]) = new FunctionalBuilderOps[M, A](a)(fcb)
和FunctionalBuilderOps
定义方法
def ~[B](mb: M[B]): FunctionalBuilder[M]#CanBuild2[A, B] = {
val b = new FunctionalBuilder(fcb)
new b.CanBuild2[A, B](ma, mb)
}
及其别名:def and[B](mb: M[B]): FunctionalBuilder[M]#CanBuild2[A, B] = this.~(mb)
包play.api.libs.json
定义了值val __ = JsPath
,因此双下划线只是单例对象JsPath
的别名。单例对象JsPath
不应与同名的类混淆。此构造在Scala中称为伴随对象。虽然Java将静态成员放在类中的常规成员旁边,但Scala会将它们放在伴随对象中。
显然,对象JsPath
同时是案例类JsPath
的伴随对象和JsPath(List.empty)
的实例。
fieldName
的类型为String
,JsPath
定义了一种方法
def \(child: String) = JsPath(path :+ KeyPathNode(child))
相当于
def \(child: String) = JsPath.apply(path :+ KeyPathNode(child))
案例类的默认应用方法' companion对象创建一个新实例,因此这相当于
def \(child: String) = new JsPath(path :+ KeyPathNode(child))
由于我们的单件对象__
是JsPath
的一个实例,其中包含path
的空列表,因此我们返回new JsPath(KeyPathNode(child))
,其中child == fieldName
它是一个以A
为例的函数。该函数将参数a
映射到元组(a, field(a)
,其中field
是作为方法addField
的参数提供的函数。它具有签名field: A => T
,因此它是一个函数,它接受A
的实例并返回T
的实例。
这可能有点令人困惑,所以让我给你一个不能在泛型上运行的示例函数。假设您要定义一个对整数进行平方的函数f
。例如,f(1)
应为1
,f(3)
应为9
。因为它需要一个整数并返回一个整数,所以它的签名是f: Int => Int
。它的实施是f = (x: Int) => x * x
。
实际上,这是不正确的 - 签名不匹配。给定的lambda函数的类型为A => (A, T)
。此外,正如答案的第一部分所指出的,方法~
返回FunctionalBuilder#CanBuild2
的实例。不幸的是,我迷失了所谓的apply
- 方法的作用,因为它是高度通用的并且使用泛型类型的隐式参数。
是的,lambda函数中的下划线是用于捕获参数的语法糖。例如,_ + _
是(x, y) => x + y
的语法糖,意味着以下两个值是等价的:
val f: (Int, Int) => Int = (x, y) => x + y
val g: (Int, Int) => Int = _ + _
所以_.isAdult
实际上只是转换为{x => x.isAdult}
,在示例中,x: Person
。
这个写入构建器背后的想法似乎是提供定义从一个Scala类到JSON的映射的方法。让我们再看看case class Person(name: String, age: Int)
。现在,我们假设我们有Person
的实例:
val odersky = Person("Martin Odersky", 56)
并希望将实例序列化为以下JSON:
{
"name": "Martin Odersky",
"age": 56
}
密钥始终是字符串,但值可以是不同的类型。所以,基本上,序列化是一个函数,它取Person
并返回一个键值对的元组,其中值总是一个字符串,即Person => ((String, String), (String, Int))
。或者,在我们不知道序列化内容的一般情况下,只是它有两个字段,对于任意类型A => ((String, B), (String, C))
,A
和{{B
C
1}}。
这是CanBuild2
模仿的内容 - 2
代表要序列化的成员数。还有CanBuild3
,CanBuild4
等等。
我们可以通过调用
来实例化适合我们示例的CanBuild2
val serializer = (__ \ "name").write[String] ~ (__ \ "age").write[Int]
此处,serializer
的类型为FunctionalBuilder[OWrites]#CanBuild2[String, Int]
现在,CanBuild2
的apply方法可用于生成第一个泛型参数的实例 - OWrites
。 apply方法的参数是一个函数,它将我们类型的实例映射到序列化(此处为Person
)到所需值类型的元组,此处为String
和Int
(表示{ {1}}和name
)。
age
此编写器现在可用于从val personWriter: OWrites[Person] = serializer((p: Person) => (p.name, p.age))
的任何实例生成JSON字符串。所以我们可以通过我们的人并获得所需的输出:
Person