Play 2.0 中文资料 - Play JSON 库使用泛型
概述
当使用基于 JSON 库的 typeclass(Unmi: typeclass 还没摸准翻译成什么词较合适,此前译作 类型类,觉得有点不妥,所以暂时保留原样) 时,可能会把泛型支持包含进这些 typeclass 中来. 针对基于终端控制查询参数,使用基本的结构作为查询结果的 REST API 来说可能是一个很好的应用方式.
Scala 对泛型的支持
给定如下基本的结构作为搜索结果:
1case class SearchResults[T](
2 elements: List[T],
3 page: Int,
4 pageSize: Int,
5 total :Int
6)Unmi 注: 上面 case class 涉及到了 Scala 的样本类的特性,Scala 会给这个类自动添加一些句法:1)添加与类名一致的工厂方法,2)参数列表中的所有参数前隐式获得了 val 前缀,即会由相应的的实例变量保持状态,3)自动添加了 toString, hashCode, 和 equals 方法。
现在你必须确保读写方法能够处理泛型,它以为你声明了隐式的参数用于支持类型 T 的读写.
1object SearchResults
2{
3 implicit def searchResultsReads[T](implicit fmt: Reads[T]): Reads[SearchResults[T]] = new Reads[SearchResults[T]] {
4 def reads(json: JsValue): SearchResults[T] = new SearchResults[T] (
5
6 (json \ "elements") match {
7 case JsArray(ts) => ts.map(t => fromJson(t)(fmt))
8 case _ => throw new RuntimeException("Elements MUST be a list")
9 },
10 (json \ "page").as[Int],
11 (json \ "pageSize").as[Int],
12 (json \ "total").as[Int]
13 )
14 }
15
16 implicit def searchResultsWrites[T](implicit fmt: Writes[T]): Writes[SearchResults[T]] = new Writes[SearchResults[T]] {
17 def writes(ts: SearchResults[T]) = JsObject(Seq(
18 "page" -> JsNumber(ts.page),
19 "pageSize" -> JsNumber(ts.pageSize),
20 "total" -> JsNumber(ts.total),
21 "elements" -> JsArray(ts.elements.map(toJson(_)))
22 ))
23 }
24}Unmi 注: 这应该是比较高阶的应用了,一般不用太关注这里的细节。而且上面的代码我运行时在行 case JsArray(ts) => ts.map(t => fromJson(t)(fmt)) 上会报错误 “type mismatch; found: Seq[T] required: List[T]”,需把 elements 类型改为 Seq[T]。可运行的完整的 SearchResults.scala 如下:
1import play.api.libs.json._
2import play.api.libs.json.Json._
3
4case class SearchResults[T](
5 elements: Seq[T],
6 page: Int,
7 pageSize: Int,
8 total :Int
9)
10
11object SearchResults
12{
13 implicit def searchResultsReads[T](implicit fmt: Reads[T]): Reads[SearchResults[T]] = new Reads[SearchResults[T]] {
14 def reads(json: JsValue): SearchResults[T] = new SearchResults[T] (
15
16 (json \ "elements") match {
17 case JsArray(ts) => ts.map(t => fromJson(t)(fmt))
18 case _ => throw new RuntimeException("Elements MUST be a list")
19 },
20 (json \ "page").as[Int],
21 (json \ "pageSize").as[Int],
22 (json \ "total").as[Int]
23 )
24 }
25
26 implicit def searchResultsWrites[T](implicit fmt: Writes[T]): Writes[SearchResults[T]] = new Writes[SearchResults[T]] {
27 def writes(ts: SearchResults[T]) = JsObject(Seq(
28 "page" -> JsNumber(ts.page),
29 "pageSize" -> JsNumber(ts.pageSize),
30 "total" -> JsNumber(ts.total),
31 "elements" -> JsArray(ts.elements.map(toJson(_)))
32 ))
33 }
34}基本的 JSON 数据类型
给定上面的类型类, 你可以轻易的创建和使用作何与
play.api.libs.json 包中基本数据类型一致的类型1val input = """{"page": 1,"pageSize":2, "total": 3, "elements" : [1, 2, 3]}"""
2val ret = play.api.libs.json.Json.parse(input).as[SearchResults[Int]]Unmi 注: 在 Controller 中用 Ok(toJson(ret)) 就能输出 JSON 字符串了。如果没有前面声明的 object SearchResults[T] 话,在执行 as[SearchResults[Int]] 就会报错:
No Json deserializer found for type models.SearchResults[Int]. Try to implement an implicit Reads or Format for this type.
更复杂的类型
一个更为复杂的 Json 对象要能被支持的话,还须确保它自己定义的序列化和反序列化的方法:
1case class Foo(name: String, entry: Int)
2
3object Foo {
4 implicit object FopReads extends Format[Foo] {
5 def reads(json: JsValue) = Foo(
6 (json \ "name").as[String],
7 (json \ "entry").as[Int])
8 def writes(ts: Foo) = JsObject(Seq(
9 "name" -> JsString(ts.name),
10 "entry" -> JsNumber(ts.entry)))
11 }
12}有了这个设置后,样本类 ‘‘Foo’’ 就可以用作 ‘‘SearchResults’’ 的一部分了(Unmi: 放在中括号中,作为 SearchResults 的泛类型).
1val input = """{"page": 1,"pageSize":2, "total": 3, "elements" : [ {"name" : "foo", "entry" : 1 }, {"name" : "bar", "entry" : 2 }]}"""
2val ret = play.api.libs.json.Json.parse(input).as[SearchResults[Foo]][版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。