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